chore: 项目已迁移至 Gitee/GitHub,仅保留迁移说明 (tombstone)
This commit is contained in:
1 parent
f2d31a620b
commit
69eaada1af
259 files changed
+1
-121455
No files matched your search
@@ -1,34 +0,0 @@
|
||||
<!-- 感谢贡献!只需两类文件:outputs/<artifactDir>/<case-id>.<ext> + models/<model-id>.json -->
|
||||
<!-- Thanks for contributing! Only two kinds of files: the artifact + the JSON registration. 详见 AGENTS.md -->
|
||||
|
||||
## 产出说明 / Run info
|
||||
|
||||
- 模型 / Model:
|
||||
- 厂商 / Vendor:
|
||||
- 运行环境 / Harness(如 ChatGPT Web、Claude Code、Codex CLI、AntiGravity…,这是工具不是模型):
|
||||
- 思考配额 / Thinking effort(如 Max、High、xhigh、Extended Pro、Thinking…):
|
||||
- 是否一次生成 / One-shot?(是/否;若有修复请在 note 里说明 / if fixed, document it in the note)
|
||||
|
||||
## 生成凭证截图 / Proof screenshot **(required)**
|
||||
|
||||
<!-- 直接把图片拖进下面,GitHub 会托管它。不要把图片文件提交进仓库。 -->
|
||||
<!-- Drag the image in below; GitHub hosts it. Do NOT commit image files into the repo. -->
|
||||
|
||||
请贴一张**生成过程截图**:你的 harness / IDE 中能看到**模型名 + 思考配额**正在产出本产物,
|
||||
作为「确实用了这套 harness + 模型 + effort」的凭证。
|
||||
Please attach a screenshot of the generation showing the **model name + effort** in your harness/IDE.
|
||||
|
||||
## 自查 / Checklist
|
||||
|
||||
- [ ] 只改了两类文件:`outputs/<artifactDir>/<case-id>.<ext>` + `models/<model-id>.json` / only the artifact + its JSON
|
||||
- [ ] **没有**手改 `README.md` / `README.en.md`(注册表合并后由 CI 自动同步)/ did **not** hand-edit the README registry (auto-synced after merge)
|
||||
- [ ] 模型 id 用短横线、以**模型**命名(非 harness),如 `deepseek-v4-flash-reasonix` / dash-case id named after the model, not the harness
|
||||
- [ ] `harness` 与 `effort` 如实填写(站点据此生成 metadata 与品牌 icon)/ `harness` and `effort` are accurate
|
||||
- [ ] 用 harness **默认形态**生成,未额外加载自带以外的 skill / 插件 / MCP / 自定义系统提示 / ran the harness as it ships — no extra skills / plugins / MCP / custom prompts
|
||||
- [ ] 每条 run 的 `note` 双语(zh + en,zh 先,无 emoji)填了来历 / bilingual `note`, zh first, no emojis
|
||||
- [ ] 同一模型换 Harness 或思考配额是**新建一个 JSON 条目** / a different harness or effort = a NEW json entry
|
||||
- [ ] `contributor` 是我的 GitHub 用户名 / `contributor` is my GitHub username
|
||||
- [ ] 已贴生成凭证截图 / attached the proof screenshot above
|
||||
- [ ] 本地 `bun scripts/validate-data.ts` 通过 / `bun scripts/validate-data.ts` passes locally
|
||||
|
||||
<!-- 规则详见 AGENTS.md。CI 只跑数据校验;README Registry 在合并到 main 后由 CI 自动重生成。 -->
|
||||
@@ -1,66 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- name: Validate contribution data
|
||||
run: bun scripts/validate-data.ts
|
||||
|
||||
sync-registry:
|
||||
# The README Registry tables are generated from models/*.json. Contributors
|
||||
# do not edit them by hand; on every push to main we regenerate and commit
|
||||
# any drift back. Commits pushed with GITHUB_TOKEN do not retrigger
|
||||
# workflows, and the message carries [skip ci] too, so there is no loop.
|
||||
if: github.event_name == 'push'
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- name: Regenerate README registry
|
||||
run: bun scripts/update-registry.ts
|
||||
- name: Commit if registry drifted
|
||||
run: |
|
||||
if git diff --quiet -- README.md README.en.md; then
|
||||
echo "Registry already in sync."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git commit -am "chore: sync README registry [skip ci]"
|
||||
git push origin HEAD:main
|
||||
|
||||
trigger-site-rebuild:
|
||||
# The site lives in the private nagi-bench-site repo (Cloudflare Pages).
|
||||
# With SITE_DISPATCH_TOKEN set the rebuild is instant; without it the
|
||||
# site's 6-hour cron picks the new data up anyway.
|
||||
if: github.event_name == 'push'
|
||||
needs: validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Dispatch data-updated to nagi-bench-site
|
||||
env:
|
||||
TOKEN: ${{ secrets.SITE_DISPATCH_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "SITE_DISPATCH_TOKEN not set; relying on the site's scheduled rebuild."
|
||||
exit 0
|
||||
fi
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/nagi-studio/nagi-bench-site/dispatches \
|
||||
-d '{"event_type":"data-updated"}'
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
site/public/outputs/
|
||||
.agents/
|
||||
.claude/
|
||||
skills-lock.json
|
||||
|
||||
# wrangler local state
|
||||
.wrangler/
|
||||
|
||||
# Local-only Claude Code guidance (kept out of the public repo)
|
||||
CLAUDE.md
|
||||
@@ -1,195 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
Guidance for humans and coding agents contributing to **NAGI BENCH** data
|
||||
(https://bench.nagi.fun/). Read this before opening a PR.
|
||||
|
||||
## What this repo is
|
||||
|
||||
The **public data repo**: community-contributed *one-shot* LLM eval artifacts.
|
||||
You add a model's raw output for a case; the site renders it verbatim. This repo
|
||||
holds **data only** — no site code, no build of the page you see.
|
||||
|
||||
## A contribution is exactly two files
|
||||
|
||||
1. **The artifact** — the raw model output, dropped at
|
||||
`outputs/<artifactDir>/<case-id>.<ext>` (e.g. a single-file `.html` or `.svg`).
|
||||
2. **The registration** — `models/<model-id>.json` declaring it.
|
||||
|
||||
Do **not** touch anything else. In particular:
|
||||
|
||||
- **Do not edit `README.md` / `README.en.md`.** The Registry tables are
|
||||
generated from `models/*.json` and synced automatically after merge. Hand
|
||||
edits are overwritten.
|
||||
- **Do not edit `cases.json`.** Cases are maintainer-owned.
|
||||
|
||||
## `models/<model-id>.json` schema
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"label": "DeepSeek V4 Flash", // human-facing model name (required)
|
||||
"vendor": "DeepSeek", // who makes the model (required)
|
||||
"harness": "Reasonix", // the tool/agent/UI you ran it in (required)
|
||||
"effort": "Max", // thinking budget / effort level (required)
|
||||
"artifactDir": "deepseek-v4-flash/reasonix-max", // optional; defaults to <model-id>
|
||||
"order": 50, // optional display sort
|
||||
"runs": {
|
||||
"skeleton-watch": {
|
||||
"note": { // REQUIRED, bilingual, zh first, no emojis
|
||||
"zh": "在 Reasonix 中以 DeepSeek V4 Flash(Max)一次生成,未修改。",
|
||||
"en": "One-shot by DeepSeek V4 Flash (Max effort) in Reasonix, unmodified."
|
||||
},
|
||||
"contributor": "your-github-username"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Naming rules
|
||||
|
||||
- **The filename IS the entry id** — lowercase letters, digits and dashes only
|
||||
(`deepseek-v4-flash-reasonix`). It is the **stable voting key** and the default
|
||||
`outputs/` folder, so name it after the **model**, not the harness. Bad:
|
||||
`reasonix-reasonix-max`. Good: `deepseek-v4-flash-reasonix`.
|
||||
- `artifactDir` may be one or two dash-case segments
|
||||
(`deepseek-v4-flash/reasonix-max`); set it when several entries share a model
|
||||
folder. Leave it out to default to the id.
|
||||
|
||||
### One entry per (model × harness × effort)
|
||||
|
||||
The same model under a different **harness** or **effort** is a **separate**
|
||||
`models/*.json` file, not an extra variant inside an existing one. "Codex",
|
||||
"Cursor", "Claude Code", "AntiGravity" name the **harness**, never the model.
|
||||
|
||||
### Multiple takes of one case
|
||||
|
||||
Ran the **same** model × harness × effort on a case more than once and want to
|
||||
keep several outputs? Make `runs.<case-id>` an **array** — this is the *only*
|
||||
case where extra artifacts live inside one entry (a different harness/effort is
|
||||
still a new file, per above):
|
||||
|
||||
```jsonc
|
||||
"runs": {
|
||||
"pelican-cycling": [
|
||||
{ "note": { "zh": "…", "en": "…" }, "contributor": "alice" },
|
||||
{ "file": "pelican-coastal-ride.svg", "note": { "zh": "…", "en": "…" }, "contributor": "bob" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
- The **first** variant may omit `file` (defaults to `<case-id>.<ext>`); **every
|
||||
extra** variant needs its own distinct, descriptive dash-case `file`
|
||||
(`pelican-coastal-ride.svg`). Two variants may not resolve to the same file.
|
||||
- **Never name an artifact `index.html`** — it is served as the folder's default
|
||||
page. Use the case name plus a short suffix (`mythos-craft-block-era.html`).
|
||||
- Each variant carries its own `note` and may set its own `contributor`, so
|
||||
different people can add takes to the same model entry.
|
||||
|
||||
### The `note` is required and bilingual
|
||||
|
||||
Every run needs `note.zh` + `note.en` documenting provenance: which harness,
|
||||
which effort, one-shot or fixed. If you applied any fix, say where the bug was
|
||||
and what you changed. **No emojis anywhere** (hard rule). zh first.
|
||||
|
||||
### Run the bare harness — NO skills, NO plugins
|
||||
|
||||
Run the model with the harness's default model and effort, but otherwise
|
||||
**stripped down**: **disable every skill and every plugin — including the ones
|
||||
the harness ships with and would auto-invoke** — and add no MCP servers and no
|
||||
custom system prompt. A skill or plugin (a built-in `design`, `gsap`, game, or
|
||||
diagram helper, an added toolset, etc.) rewrites the output far more than the
|
||||
model itself does, so a run with any of them active measures the tooling, not the
|
||||
model × harness × effort. It is not comparable to other entries and **will not be
|
||||
accepted**. Disclosing it in `note` does not fix this: leaderboard identity is
|
||||
only model × harness × effort, so a modified setup gets no separate identity — it
|
||||
would just be mixed into a row it is not comparable with.
|
||||
|
||||
### Keep the artifact anonymous — no identity leaks
|
||||
|
||||
**The output must not reveal which model, vendor, or harness produced it.** Strip
|
||||
any `<!-- Generated by ... -->` header, watermark, "as an AI / as a large
|
||||
language model" phrasing, or signature in visible text or code comments before
|
||||
you submit. Blind Arena battles depend on outputs being anonymous, so a
|
||||
self-identifying artifact invalidates the entry.
|
||||
|
||||
## React cases — contribute `src/` only
|
||||
|
||||
Some cases (marked `kind: "react"` in `cases.json`) ask for a real multi-file
|
||||
React project instead of a single file. For these your artifact is **the `src/`
|
||||
tree only** — multiple files and nested folders are fine — dropped at
|
||||
`outputs/<artifactDir>/src/`. The site owns the whole toolchain and builds every
|
||||
React case with **one shared scaffold** (its own installed React + Vite), so
|
||||
there is no per-project dependency setup:
|
||||
|
||||
- **Do NOT include `package.json`, a lockfile, `node_modules`, `index.html`, or
|
||||
any build / Vite / TS config.** The scaffold supplies all of it.
|
||||
- **Use only libraries the site already ships** (React + ReactDOM, plus whatever
|
||||
is in its `package.json`). The build installs nothing new — you cannot pull in
|
||||
arbitrary npm packages.
|
||||
- **The entry point is `src/main.tsx`**, mounting your app into `<div id="root">`.
|
||||
- At build time the `src/` is bundled and inlined into **one self-contained HTML
|
||||
file** that the normal viewer serves verbatim. The anonymity rule above still
|
||||
applies: no model / vendor / harness identity anywhere in the code.
|
||||
|
||||
## Agent cases — maintainer-run only
|
||||
|
||||
Some cases (marked `kind: "agent"` in `cases.json`, currently `turf-war`) ask
|
||||
the model to write a game-playing agent that battles other models' agents,
|
||||
ranked by real wins instead of votes. These cases are **not open for artifact
|
||||
PRs**, by design:
|
||||
|
||||
- Policy files are deliberately kept off the public repo — if submissions were
|
||||
public, later entrants could read earlier policies and prompt their model to
|
||||
counter them, which breaks the one-shot fairness of the case.
|
||||
- All runs are produced by the maintainer, feeding every model the identical
|
||||
canonical prompt one-shot (no test runs, no iteration, no extra hints).
|
||||
Battles are refereed server-side inside an instrumented sandbox with a
|
||||
deterministic compute budget; the browser only replays action logs.
|
||||
- The registration in `models/<model-id>.json` (a `runs.<case-id>` entry with
|
||||
the usual bilingual provenance note) is the only public trace; there is no
|
||||
`outputs/` artifact for these cases.
|
||||
- Want a model or harness combo included? Open an issue and the maintainer
|
||||
will run it.
|
||||
|
||||
## Review guidelines
|
||||
|
||||
NAGI BENCH stores raw benchmark artifacts. Reviewers, including Codex Review,
|
||||
must **not** request fixes for gameplay, rendering, animation, JavaScript
|
||||
correctness, UX, visual quality, performance, accessibility, or any other
|
||||
internal behavior of files under `outputs/`. Broken or low-quality artifacts are
|
||||
valid benchmark data when their provenance is documented.
|
||||
|
||||
Only flag:
|
||||
|
||||
- visible model, vendor, harness, contributor, or other identity leaks inside
|
||||
`outputs/` that would break blind Arena review;
|
||||
- contribution format or schema issues that violate this file or would fail
|
||||
`bun scripts/validate-data.ts`.
|
||||
|
||||
Do not ask contributors to improve or repair the submitted artifacts themselves.
|
||||
If an artifact bug matters, mention it only as optional context for maintainers,
|
||||
not as required PR feedback.
|
||||
|
||||
## Before you open the PR
|
||||
|
||||
```bash
|
||||
bun scripts/validate-data.ts # the exact schema check CI runs
|
||||
```
|
||||
|
||||
This enforces: dash-only ids, required bilingual `note`, declared artifacts
|
||||
exist and are non-empty, no orphan files under `outputs/`. Green here = green CI.
|
||||
|
||||
You do **not** need to run `update-registry.ts` — README is synced for you after
|
||||
merge.
|
||||
|
||||
### Attach a screenshot
|
||||
|
||||
The PR template asks for a **screenshot of the generation** — your harness/IDE
|
||||
showing the model name + effort while producing this artifact. It is the proof
|
||||
that the declared `harness` / `model` / `effort` are real. Paste it into the PR
|
||||
body (GitHub hosts it); **do not commit image files** into the repo.
|
||||
|
||||
## Conventions
|
||||
|
||||
- No emojis in any data, note, or UI-bound text.
|
||||
- `outputs/` files are served verbatim in a sandboxed iframe. Keep them
|
||||
self-contained; external resources should be well-known CDNs only.
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 shuiyu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
# NAGI BENCH
|
||||
|
||||
[中文](./README.md) | English
|
||||
|
||||
One-shot LLM eval cases by NAGI STUDIO: same prompt, different model x harness x effort combinations, one attempt and no retries — a side-by-side record of the runnable artifacts they ship.
|
||||
|
||||
**Live site:** https://bench.nagi.fun/
|
||||
|
||||
<p>
|
||||
<a href="https://x.com/Nag1ovo" target="_blank"><img src="https://img.shields.io/badge/@Nag1ovo-000000?style=flat-square&logo=x&logoColor=white" alt="X"></a>
|
||||
<a href="https://discord.gg/TEUFxdMbGb" target="_blank"><img src="https://img.shields.io/badge/Discord-Join-5865F2?style=flat-square&logo=discord&logoColor=white" alt="Discord"></a>
|
||||
<a href="https://www.xiaohongshu.com/user/profile/5d366136000000001101950a" target="_blank"><img src="https://img.shields.io/badge/小红书-@Nagi--ovo-FF2442?style=flat-square" alt="Xiaohongshu"></a>
|
||||
<a href="https://space.bilibili.com/312249633" target="_blank"><img src="https://img.shields.io/badge/Bilibili-@卡普迪姆-FB7299?style=flat-square&logo=bilibili&logoColor=white" alt="Bilibili"></a>
|
||||
</p>
|
||||
|
||||
|
||||
## Models vs harnesses
|
||||
|
||||
> **Model + Harness = Agent** — a model is just weights; wrap it in a runtime and it becomes an agent that can actually do the work. That agent is the unit this bench evaluates.
|
||||
|
||||
- **Model**: the weights and inference engine itself — GPT-5.5, Gemini 3.1 Pro, Claude Fable 5. Reasoning effort is a capability dial of the model itself: the same weights at high vs low effort reason at depths that feel like two different models, so effort belongs to the "model" dimension alongside the weights — judging a model while ignoring its effort isn't fair play. This bench defaults to max and labels the effort of every combination.
|
||||
- **Harness**: the product/scaffolding wrapping the model — Codex CLI, Cursor, AntiGravity, the Claude web app. It controls tool calls, system prompts, context management and continuation strategy, and often shapes the outcome as much as the model does.
|
||||
- The unit of evaluation here is therefore an **Agent**: the same model (including its effort) under a different harness is a different agent and a separate entry (GPT-5.5 Pro and GPT-5.5 running in Codex CLI at xhigh are two agents from the same model family).
|
||||
- **The harness is evaluated as it ships, out of the box**: do not load skills / plugins / MCP servers / custom system prompts beyond what it bundles by default — they shift the output as much as swapping the harness does and break comparability between agents. Such runs are **not accepted** (the leaderboard identity is just model × harness × effort, so a `note` does not give them a separate identity).
|
||||
|
||||
## Registry
|
||||
|
||||
<!-- registry:start -->
|
||||
> This table is generated from `models/*.json` by `bun scripts/update-registry.ts`; CI re-syncs it automatically after merge — contributors need not hand-edit it.
|
||||
|
||||
| Model | Vendor | Harness x Effort | Runs |
|
||||
|---|---|---|---|
|
||||
| Claude Fable 5 | Anthropic | Claude Web App · Max<br>Claude Code · High<br>Claude Code · Max<br>Claude Code · xhigh<br>Cursor · High | 11 |
|
||||
| Claude Sonnet 5 | Anthropic | Claude Code · Max | 01 |
|
||||
| Claude Opus 4.8 | Anthropic | Claude Code · Max<br>Claude Code · xhigh | 08 |
|
||||
| Claude Opus 4.7 | Anthropic | Claude Code · Max<br>Cursor · Max | 05 |
|
||||
| Claude Opus 4.6 | Anthropic | Claude Code · Max<br>Cursor · Max | 04 |
|
||||
| Claude Opus 4.5 | Anthropic | Cursor · Thinking | 02 |
|
||||
| Claude Sonnet 4.6 | Anthropic | Claude Code · Max<br>Cursor · High | 03 |
|
||||
| Claude Haiku 4.5 | Anthropic | Claude Code · Default | 02 |
|
||||
| GPT-5.6-Sol | OpenAI | Codex CLI · ultra<br>Codex CLI · max<br>Codex CLI · xhigh | 09 |
|
||||
| GPT-5.6-Terra | OpenAI | Codex CLI · ultra<br>Codex CLI · xhigh | 04 |
|
||||
| GPT-5.5 Pro | OpenAI | ChatGPT Web · Extended Pro | 03 |
|
||||
| GPT-5.5 | OpenAI | Codex CLI · xhigh | 03 |
|
||||
| GPT-5.4 | OpenAI | Cursor · xhigh<br>Codex CLI · xhigh | 06 |
|
||||
| GPT-5.3 Codex | OpenAI | Cursor · xhigh | 02 |
|
||||
| GPT-5.2 | OpenAI | Cursor · xhigh | 02 |
|
||||
| Gemini 3.1 Pro | Google | AntiGravity · High<br>Cursor · Default<br>Gemini Web · Deep Think<br>Google AI Studio · High | 11 |
|
||||
| DeepSeek V4 Flash | DeepSeek | Claude Code · Max<br>Reasonix · Max | 02 |
|
||||
| Gemini 3.5 Flash | Google | AntiGravity · High<br>Cursor · Default<br>Google AI Studio · High | 06 |
|
||||
| Grok 4.5 | xAI | Grok Build TUI · High | 03 |
|
||||
| Grok Build | xAI | Grok Build TUI · Max | 02 |
|
||||
| Grok 4.3 | xAI | Cursor · Default | 02 |
|
||||
| Nex-N2-Pro | Nex-AGI | Claude Code · Max | 02 |
|
||||
| Composer 2.5 | Cursor | Cursor · Max<br>Cursor · Default<br>Grok Build TUI · Default | 05 |
|
||||
| Mistral Medium 3.5 | Mistral AI | Vibe · Thinking | 02 |
|
||||
| DeepSeek-V4-Pro | DeepSeek | Claude Code · Max | 03 |
|
||||
| Doubao Seed 2.0 Pro | ByteDance | Doubao Web · Pro Mode | 01 |
|
||||
| Doubao Seed 2.0 Mini | ByteDance | Doubao Web · Fast Mode | 01 |
|
||||
| MiMo V2.5 | Xiaomi | MiMo Code · High | 03 |
|
||||
| MiMo v2.5 Pro | Xiaomi | Claude Code · Max<br>MiMo Code · Max | 03 |
|
||||
| MiMo v2.5 Pro UltraSpeed | Xiaomi | Web · Default | 05 |
|
||||
| Kimi K3 | Moonshot AI | Kimi Code · Max<br>Kimi Web · Max | 04 |
|
||||
| Kimi K2.7-Code | Moonshot AI | Kimi Code · Thinking | 03 |
|
||||
| Kimi K2.6 | Moonshot AI | Kimi Code · Thinking | 02 |
|
||||
| MiniMax M3 | MiniMax | MiniMax Code Web · Thinking | 02 |
|
||||
| MiniMax M2.7 | MiniMax | MiniMax Code Web · Thinking | 01 |
|
||||
| GLM-5.2 | Zhipu AI | ZCode · Max | 04 |
|
||||
| GLM-5.1 | Zhipu AI | ZCode · Max | 01 |
|
||||
| GLM-5 Turbo | Zhipu AI | ZCode · Thinking | 03 |
|
||||
| Qwen3.7-Max | Alibaba | Qoder · Default | 02 |
|
||||
| Step 3.7 Flash | StepFun | Claude Code · High | 02 |
|
||||
| Inkling | Thinking Machines Lab | Tinker Web · xHigh | 02 |
|
||||
<!-- registry:end -->
|
||||
|
||||
## Arena blind battles & community board
|
||||
|
||||
- Each case defaults to **Arena mode**: two anonymous artifacts battle (A / B / Neither) and identities reveal only after you vote; switch to **Demo Show** to browse everything (tabs are grouped by model — a model with several harnesses can be viewed singly or side by side).
|
||||
- Casting 03 votes total unlocks the **community leaderboard**, ranked by Bradley-Terry scores (the LMSYS Chatbot Arena approach); "neither" counts separately as both-bad.
|
||||
- Votes stay in the browser by default; deploying `worker/` (Cloudflare Worker + D1 + Turnstile, free tier) upgrades the board to sitewide crowdsourced data — see [worker/README.md](./worker/README.md).
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
outputs/<artifact-dir>/<case-id>.<ext> raw model artifacts (HTML / SVG), filename = case id
|
||||
models/<agent-id>.json agent registry: label / vendor / harness / effort / artifactDir / order / run notes
|
||||
cases.json case definitions (bilingual prompts, maintainer-owned)
|
||||
scripts/validate-data.ts data validation (CI runs it on every PR)
|
||||
scripts/update-registry.ts README Registry table generation (CI auto-syncs after merge)
|
||||
```
|
||||
|
||||
> The site and vote-backend source lives in the private `nagi-bench-site` repo, deployed on Cloudflare Pages; this repo is the site's data source.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
bun scripts/validate-data.ts # the same data validation CI runs (run this before submitting)
|
||||
bun scripts/update-registry.ts # optional: preview the Registry tables locally; CI syncs them after merge
|
||||
```
|
||||
|
||||
## Contributing a run
|
||||
|
||||
> Full rules live in [**AGENTS.md**](./AGENTS.md) (the contributor / AI-agent guide).
|
||||
|
||||
> **If you are an AI agent (Claude Code / Codex / Cursor, etc.), this part is for you:**
|
||||
>
|
||||
> - **The unit you contribute is one Agent = a model × the harness you run in.** The `<agent-id>` (lowercase letters / digits / dashes) encodes that pairing — e.g. `gpt-5-5-pro`, `claude-fable-5-cc` (cc = Claude Code). The same model in a different harness is a different Agent with a different id. The `<agent-id>` is the vote and leaderboard identity; do not rename it just to reorganize files.
|
||||
> - **There are five cases right now**, defined in [`cases.json`](./cases.json): `mythos-craft` (HTML, a playable voxel world), `pelican-cycling` (SVG, a pelican cycling by the sea), `skeleton-watch` (HTML, a skeleton mechanical watch), `cs-dust2` (React, a multi-file project), and `turf-war` (JS agents battling head-to-head, ranked by objective wins). The prompt lives in `cases.json` — **use it verbatim, do not paraphrase**. Note: `turf-war` does **not** accept artifact PRs — to keep earlier policies unscoutable, every run is executed and submitted personally by the maintainer Nagi ([@Nagi-ovo](https://github.com/Nagi-ovo)); open an issue to request a model.
|
||||
> - **To find what's missing**: read `cases.json` (all cases) and `models/*.json` (what each Agent has run) — a `<case-id>` absent from an Agent's json is an open slot; any row in the script-generated Registry table above whose "Runs" count is below the number of cases is an Agent missing a case; a brand-new Agent can fill them.
|
||||
> - **Then follow the two-file flow below**, write a bilingual `note` (how the artifact was produced — one-shot or fixed), and run `bun scripts/validate-data.ts` until it passes before opening a PR.
|
||||
|
||||
Contributions are data-only — no code changes needed:
|
||||
|
||||
1. Artifact: preferably `outputs/<base-model>/<harness-effort>/<case-id>.<ext>` (e.g. `outputs/gpt-5-5-pro/chatgpt-web-extended-pro/pelican-cycling.svg`).
|
||||
2. Registration: `models/<agent-id>.json` (new file for a new combination; add to `runs` for an existing one) with `artifactDir` pointing at the artifact folder — see the schema example in the Chinese README.
|
||||
|
||||
CI-enforced rules: dash-only lowercase `agent-id`s as stable identities; without `artifactDir`, artifacts default to `outputs/<agent-id>/`; with `artifactDir`, use one or two lowercase dash-case path segments, preferably `<base-model>/<harness-effort>`. A bilingual provenance `note` is required per run (which harness, what effort, one-shot or fixed); every declared run must have its artifact; multiple versions of the same combination x case go in an array with distinct `file` names (e.g. `pelican-cycling-2.svg`). Set `contributor` to your GitHub username — the site shows your avatar linked to your profile next to the run. Fill in `harness` and `effort` truthfully for new combinations: the case page renders Harness / Effort metadata chips from these fields and auto-matches brand icons for the model, vendor and harness (via [lobe-icons](https://github.com/lobehub/lobe-icons)) — contributors never touch icons.
|
||||
|
||||
PRs get data validation from CI; merges to `main` rebuild the site automatically (usually instant, at most 6 hours).
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
你好呀~这个仓库已经搬到更好用的地方啦。
|
||||
|
||||
为了让代码管理更清爽、多平台同步更省心,我们把原先分散的镜像统一收拢到
|
||||
**Gitee** 与 **GitHub** 主仓维护,这里就只保留这张"迁移小纸条"咯。
|
||||
为了让代码管理更清爽、多平台同步更省心,我们把原先分散的镜像统一收拢到主仓维护,这里就只保留这张"迁移小纸条"咯。
|
||||
|
||||
📦 新家在这里:
|
||||
- Gitee:https://gitee.com/shuiyu1123/nagi-bench
|
||||
|
||||
-120
@@ -1,120 +0,0 @@
|
||||
[
|
||||
{
|
||||
"id": "mythos-craft",
|
||||
"index": "01",
|
||||
"kind": "html",
|
||||
"title": {
|
||||
"zh": "神话境界",
|
||||
"en": "Mythos Craft"
|
||||
},
|
||||
"tagline": {
|
||||
"zh": "单个 HTML 文件,一次生成一款《我的世界》式的可玩 3D 体素世界。",
|
||||
"en": "A playable Minecraft-style 3D voxel world, generated as a single HTML file in one shot."
|
||||
},
|
||||
"prompt": {
|
||||
"zh": "你是一个顶级的资深游戏开发者。请为我使用单 HTML5 和 Canvas(或 Three.js)编写一个《我的世界》克隆版。请包含以下核心功能:\n\n1. 一个基于柏林噪声(Perlin Noise)生成的无限或可滚动的 3D 地形。\n2. 第一人称视角,支持使用 WASD 移动和鼠标环视。\n3. 支持至少 3 种方块类型(如:泥土、草地、石头等),并支持鼠标左键破坏方块,右键放置方块。材质要尽可能还原。\n4. 要有一个内置的、极其丰富的神话幻想中的建筑和地形等,由你自行设计。",
|
||||
"en": "You are a top-tier veteran game developer. Build me a Minecraft clone using a single HTML5 file and Canvas (or Three.js). Include the following core features:\n\n1. An infinite or scrollable 3D terrain generated from Perlin noise.\n2. A first-person view with WASD movement and mouse look.\n3. At least 3 block types (e.g. dirt, grass, stone), with left-click to break and right-click to place blocks. Keep the textures as faithful as possible.\n4. A built-in, extremely rich set of mythic-fantasy buildings and terrain, designed at your own discretion."
|
||||
},
|
||||
"tags": [
|
||||
"HTML",
|
||||
"3D",
|
||||
"GAME",
|
||||
"ONE-SHOT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pelican-cycling",
|
||||
"index": "02",
|
||||
"kind": "svg",
|
||||
"title": {
|
||||
"zh": "鹈鹕海边骑行",
|
||||
"en": "Pelican by the Sea"
|
||||
},
|
||||
"tagline": {
|
||||
"zh": "经典 SVG 测评题:让模型用代码「画」出一只骑自行车的鹈鹕。",
|
||||
"en": "The classic SVG eval: make the model \"draw\" a pelican riding a bicycle, in code."
|
||||
},
|
||||
"prompt": {
|
||||
"zh": "制作一个逼真的、生动的、在海边骑着自行车的鹈鹕 <svg>",
|
||||
"en": "Create a realistic, lively <svg> of a pelican riding a bicycle by the sea."
|
||||
},
|
||||
"tags": [
|
||||
"SVG",
|
||||
"ILLUSTRATION",
|
||||
"ONE-SHOT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "skeleton-watch",
|
||||
"index": "03",
|
||||
"kind": "html",
|
||||
"title": {
|
||||
"zh": "镂空机械表",
|
||||
"en": "Skeleton Watch"
|
||||
},
|
||||
"tagline": {
|
||||
"zh": "单个 HTML 文件,一次生成一只可旋转、可爆炸视图的 3D 镂空机械腕表,机芯全可见。",
|
||||
"en": "A single HTML file: a rotatable, exploded-view 3D skeleton mechanical watch with a fully visible movement, generated in one shot."
|
||||
},
|
||||
"prompt": {
|
||||
"zh": "创建一个精美、高细节的交互式 3D 镂空机械表。\n\n核心要求:\n• 完全可见的复杂机芯:包括主发条、齿轮系、带游丝的摆轮、擒纵机构、夹板、红宝石轴承等。\n• 真实的金属材质、蓝宝石水晶玻璃、反射、阴影,以及奢华腕表摄影级别的照明效果。\n• 荧光指针与刻度。\n• 交互式控制:支持旋转手表、切换爆炸视图、悬停/点击时高亮显示的零件标签、机芯信息面板。\n• 流畅的动画,达到专业级产品展示质量。\n• 深色优雅背景,带有细微的反射效果。\n\n在一次回复中尽可能真实且令人惊叹地完成。输出为一个完整的、自包含的单 HTML 文件,使用 Three.js (CDN),所有代码均采用内联形式。",
|
||||
"en": "Create an exquisite, highly detailed, interactive 3D skeleton mechanical watch.\n\nCore requirements:\n• A fully visible, complex movement: mainspring, gear train, balance wheel with hairspring, escapement, bridges, ruby bearings, and so on.\n• Realistic metal materials, sapphire crystal glass, reflections, shadows, and luxury-watch photography-grade lighting.\n• Luminous hands and markers.\n• Interactive controls: rotate the watch, toggle an exploded view, part labels highlighted on hover/click, and a movement info panel.\n• Smooth animation at professional product-showcase quality.\n• A dark, elegant background with subtle reflections.\n\nMake it as realistic and stunning as possible in a single response. Output a complete, self-contained single HTML file using Three.js (CDN), with all code inline."
|
||||
},
|
||||
"tags": [
|
||||
"HTML",
|
||||
"3D",
|
||||
"THREE.JS",
|
||||
"ONE-SHOT"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "turf-war",
|
||||
"index": "04",
|
||||
"kind": "agent",
|
||||
"title": {
|
||||
"zh": "圈地对决",
|
||||
"en": "Turf War"
|
||||
},
|
||||
"tagline": {
|
||||
"zh": "每个模型一次性写出一个圈地智能体,在同一张网格战场上真刀真枪对战——排名不靠投票,靠客观胜负。",
|
||||
"en": "Each model writes a territory-grabbing agent in one shot; agents battle head-to-head on the same grid — ranked by objective wins, not votes."
|
||||
},
|
||||
"prompt": {
|
||||
"zh": "你要为一个 1v1 的「圈地对决」(Turf War) 环境编写一个 JavaScript 智能体。这是一个双人零和、双方同时行动的博弈。你的目标只有一个:在与多个未知对手、多张随机地图的循环赛中拿到尽可能高的胜率。对手由其它同等水平的 Agent(模型 × Harness 组合)编写——不要依赖「对手会犯错」的脆弱套路,任何固定模式都可能被针对;但也不要为求稳放弃进攻,平局只得 0.5 分,一个只求不败的智能体拿不到高排名。这是你唯一一次提交:没有反馈、没有试跑、没有重试,成绩公开计入排行榜。请把你能写出的最强智能体写进这个文件。\n\n【环境规则】\n\n- 棋盘为 40×40 网格,坐标 (x, y),x 向右,y 向下,范围 0–39。每局按种子生成若干 180° 中心对称的岩石格(不可通行),出生点附近不会有岩石。\n- 双方从对角出生点出发,初始拥有出生点周围 3×3 领地。每 tick 双方同时移动一格,方向为 up / down / left / right 之一;全场共 1200 tick。\n- 在自己领地外移动会留下尾巴。回到自己领地的瞬间,你的尾巴以及被「尾巴 + 领地」围住的区域全部变成你的领地(包括对方领地)。围住判定:从棋盘边界向内 flood fill(岩石可穿过但不可被占有),无法到达的格子归你。\n- 任何人(包括你自己)踩到某玩家的尾巴,该尾巴的主人立即死亡。双方同 tick 进入同一格或互相穿过,双方同时死亡。撞岩石或出界,自己死亡。击杀判定基于本 tick 开始前的尾巴状态,双方完全对等。\n- 死亡后尾巴消失、领地保留;3 tick 后在出生点重生并重新获得周围 3×3 领地(覆盖任何归属),重生后 10 tick 无敌(不会死亡也不能击杀)。\n- 同 tick 结算顺序固定:移动 → 死亡判定 → 圈地 → 重生计时。1200 tick 结束时领地格数多者获胜,相等为平局。\n\n【接口】你必须交付一个 JS 文件:在能创建文件的环境中,按文末【本次运行】给出的文件名先创建文件,再分多次编辑逐段写入,把整份策略累积写完——不要在单次写入里把整份代码一次塞进去。原因:单次模型响应的输出(含推理 thinking + 正文 + 工具调用内容)有硬上限,一次性写入大文件容易撞上限而失败,而分多次编辑时每次都是独立的新预算。纯对话环境无法建文件时,才直接输出完整文件内容(此时请让策略尽量紧凑)。全程不得运行或试跑代码。文件顶层定义:\n\n```js\nfunction createAgent(config) {\n // config = { size: 40, totalTicks: 1200 }\n // 可以在闭包里保存任意记忆(允许并鼓励在对局中在线建模对手)\n return {\n act(obs) {\n return \"up\"; // \"up\" | \"down\" | \"left\" | \"right\"\n }\n };\n}\n```\n\nobs 每 tick 传入,字段:\n\n- obs.tick:当前 tick,从 0 起\n- obs.grid[y][x]:0 空地 · 1 我的领地 · 2 我的尾巴 · 3 对方领地 · 4 对方尾巴 · 5 岩石。grid 数组每 tick 会被复用,不要跨 tick 持有引用,需要历史请自行拷贝\n- obs.me / obs.opp:{ x, y, dir, alive, respawnIn, invincibleFor }\n- obs.scores:{ me, opp },双方当前领地格数\n- obs.rng():[0,1) 确定性随机数;需要随机时只准用它\n\n另外,请在返回的对象里附带 name 字段(与 act 并列):给你的算法起一个不超过 5 个字、有意思的名字,例如 { name: \"...\", act(obs) {...} }。\n\n视角是自我中心的:无论执哪一边,你都是 me。信息完全公开,没有战争迷雾。非法动作、抛异常或超出算力预算,该 tick 视为沿原方向直行。\n\n【运行时约束】\n\n- 纯 JavaScript(ES2020),单文件;禁止 import/require、网络、存储、Math.random、Date(运行环境会封禁它们)。\n- 算力限制是确定性的:你的代码会被自动插桩计数,每 tick 预算为 200,000 次「循环迭代 + 函数调用」。参考:一次 40×40 全图 BFS 约 2,000–6,000 次。超出预算该 tick 视为直行。\n- 完成本任务只依靠你自身的知识与推理:禁止联网搜索、禁止查阅外部文档或代码库、禁止调用任何 skill/插件/外部工具、禁止试运行代码。\n- 保持代码紧凑自足:聚焦一套有效策略,不要内联大型预计算表、穷举查找表或冗长注释。\n- 交付前静态自查:合法 ES2020、顶层定义了 createAgent、无 import / Math.random / Date。\n\n【评测】你的智能体将与其它 Agent 编写的智能体进行多局循环赛,每局地形随机、先后手轮换,按胜负用 Bradley-Terry 排名。请深思熟虑后一次性交付唯一、完整、可直接运行的 JS 文件,除该文件外不要有任何解释或多余文件。\n\n【本次运行】(此行由维护者在运行前填写,其余内容对所有模型一字不差)模型 {model} × Harness {harness} × 思考配额 {effort};如需创建文件,文件名为 {agent-id}.js。",
|
||||
"en": "Write a JavaScript agent for a 1v1 \"Turf War\" environment. This is a two-player, zero-sum, simultaneous-move game. Your sole objective is to maximize your win rate in a round-robin against multiple unknown opponents across randomized maps. Opponents are written by other, equally capable Agents (an Agent = a model × harness combination) — do not rely on brittle patterns that assume opponent mistakes, as any fixed pattern can be exploited; but do not trade wins for safety either: a draw scores only 0.5, and an agent that merely avoids losing will not rank. This is your only submission: no feedback, no test runs, no retries, and the result is public on a leaderboard. Write the strongest agent you can into this file.\n\n[Environment]\n\n- The board is a 40×40 grid, coordinates (x, y), x rightward, y downward, range 0–39. Each match seeds a set of 180°-centrally-symmetric rock cells (impassable); no rocks appear near the spawns.\n- Both players start from opposite corners owning a 3×3 territory around their spawn. Each tick both players move one cell simultaneously (up / down / left / right); a match lasts 1200 ticks.\n- Moving outside your own territory leaves a trail. The moment you re-enter your own territory, your trail plus every region enclosed by trail + territory becomes yours (including the opponent's territory). Enclosure test: flood fill inward from the board border (rocks are transparent to the flood but can never be owned); unreachable cells are captured.\n- If anyone (including yourself) steps on a player's trail, that trail's owner dies immediately. Entering the same cell on the same tick, or swapping cells, kills both players. Hitting a rock or leaving the board kills you. Kill checks use the trail state from the start of the tick, so both sides are perfectly symmetric.\n- On death your trail vanishes but your territory is kept; you respawn at your spawn point after 3 ticks with a fresh 3×3 territory (overwriting any ownership) and 10 ticks of invincibility (you cannot die or kill).\n- Per-tick resolution order is fixed: moves → deaths → capture → respawn countdown. After 1200 ticks the larger territory wins; equal is a draw.\n\n[Interface] You must deliver one JS file: if your environment can create files, first create the file under the name given in the [This run] line, then write the whole policy by editing it in several steps, adding a section at a time — do NOT cram the entire file into one write. Reason: a single model response has a hard output cap (covering reasoning/thinking + text + tool-call content together), so writing a large file all at once can hit that cap and fail, whereas each separate edit gets its own fresh budget. Only when your environment cannot create files should you print the complete file content instead (and keep the policy compact in that case). Never run or test-run the code at any point. At top level it defines:\n\n```js\nfunction createAgent(config) {\n // config = { size: 40, totalTicks: 1200 }\n // keep any memory in the closure (in-match opponent modeling is allowed and encouraged)\n return {\n act(obs) {\n return \"up\"; // \"up\" | \"down\" | \"left\" | \"right\"\n }\n };\n}\n```\n\nobs is passed every tick:\n\n- obs.tick: current tick, starting at 0\n- obs.grid[y][x]: 0 empty · 1 my territory · 2 my trail · 3 opponent territory · 4 opponent trail · 5 rock. Grid arrays are reused across ticks — do not hold references across ticks; copy if you need history\n- obs.me / obs.opp: { x, y, dir, alive, respawnIn, invincibleFor }\n- obs.scores: { me, opp }, current territory cell counts\n- obs.rng(): deterministic random in [0,1); it is the only allowed source of randomness\n\nAlso include a name field on the returned object (next to act): give your algorithm a fun, memorable name of at most one short word (~10 characters), e.g. { name: \"...\", act(obs) {...} }.\n\nThe view is ego-centric: whichever side you play, you are me. Information is fully public — no fog of war. An invalid action, a thrown exception, or exceeding the compute budget makes you continue straight for that tick.\n\n[Runtime]\n\n- Pure JavaScript (ES2020), single file; import/require, network, storage, Math.random and Date are forbidden (the runtime blocks them).\n- The compute limit is deterministic: your code is automatically instrumented, with a per-tick budget of 200,000 loop iterations + function calls. Reference: one full-board 40×40 BFS costs about 2,000–6,000. Exceeding the budget means you walk straight that tick.\n- Rely solely on your own knowledge and reasoning: no web search, no consulting external docs or codebases, no skills/plugins/external tools, no test-running code.\n- Keep the code compact and self-contained: commit to one effective strategy; do not inline large precomputed tables, exhaustive lookup tables, or verbose commentary.\n- Static self-check before you deliver: valid ES2020, createAgent defined at top level, Line truncated
|
||||
},
|
||||
"tags": [
|
||||
"JS",
|
||||
"AGENT",
|
||||
"1V1",
|
||||
"ONE-SHOT"
|
||||
],
|
||||
"credit": {
|
||||
"text": "Inspired by Jiayi Weng, Learning Beyond Gradients",
|
||||
"url": "https://trinkle23897.github.io/learning-beyond-gradients/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "cs-dust2",
|
||||
"index": "05",
|
||||
"kind": "react",
|
||||
"title": {
|
||||
"zh": "CS Dust2 5v5",
|
||||
"en": "CS Dust2 5v5"
|
||||
},
|
||||
"tagline": {
|
||||
"zh": "React + three.js 从零手写的 3D 第一人称射击原型,复刻 Dust2 的 5v5 对战。",
|
||||
"en": "A from-scratch 3D FPS in React + three.js: a 5v5 battle on a rebuilt Dust2."
|
||||
},
|
||||
"prompt": {
|
||||
"zh": "用 **React + TypeScript + three.js** 从零开发一个可在浏览器直接运行的 3D 第一人称射击原型:复刻 Dust2 核心区域的 5v5 对战。你可以自行搭建项目、安装依赖、运行调试并迭代,直到它能正常游玩。\n\n## 交付形式\n\n- 一个标准 **Vite + React 18 + TypeScript** 项目,含完整 `package.json`、`vite.config.ts`、`index.html`、`src/`,`npm install && npm run dev` 即可打开游玩。\n- **不使用任何外部资产文件**(3D 模型、贴图、音频一律不用)。地图、角色、武器全部在代码里用几何体 procedural 生成,声音用 Web Audio 程序化合成。第三方库只用 three / react / react-dom;寻路、状态机、物理、渲染循环都自己实现,不引入现成游戏引擎。\n\n## 1. Dust2 地图\n\n在 3D 世界里搭出空间尺度合理的 Dust2 经典区域,**必须完整包含**:T 出生点、CT 出生点、A大、A点、中门(含可穿过的中门门体)、猫道、B洞、B点,并保证这些区域按 Dust2 的实际走位彼此连通。要么全部包含,不要交出缺区域的残缺版本。所有墙体、箱子都有物理碰撞,玩家和 AI 都无法穿墙。\n\n## 2. 角色\n\n在没有外部模型的前提下,用代码搭出**类人型角色**:必须有独立的头、躯干、双臂、双腿——**严禁用胶囊体或圆柱体等基础几何体敷衍成一根柱子**。CT 与 T 两个阵营在外观上要能一眼区分,角色默认是持枪姿态,武器随角色朝向移动。\n\n## 3. 武器系统\n\n做一套可扩展的武器数据系统,支持在主武器 / 副武器 / 近战之间切换,并有对应的第一人称手持模型。至少还原以下手感差异:\n\n- **AK-47**:高伤害,开火后坐力剧烈(准星扩散 + 视角上扬明显)。\n- **M4A4**:中等伤害,后坐力低,射速高。\n- **AWP**:单发致命,射速极低,**右键开镜**(3D 视野拉近 + 2D 瞄准镜遮罩覆盖)。\n- **手枪**:至少 Glock(T 默认)、USP(CT 默认)、沙鹰,用于手枪局;伤害低于步枪、沙鹰介于两者之间且弹匣小。\n- **近战刀**。\n\n命中判定按 **Hitbox 分区**(头 / 胸 / 腹 / 手臂 / 腿),伤害各不相同,**爆头伤害是身体的两倍**。有护甲时按护甲减伤。\n\n## 4. 手枪局\n\n支持\"手枪局\"开局:全员只带默认手枪、没有主武器、护甲按手枪局配置。这是标准 CS 的第一回合形态。\n\n## 5. C4 炸弹与回合胜负\n\n- 回合开始时 C4 交给某个 T;持包者阵亡后炸弹掉落,可被其他 T 捡起。\n- **下包**:持包 T 在 A点或 B点范围内可安放 C4,安放后进入倒计时。\n- **拆包**:CT 站在已安放的 C4 处,持续一段时间完成拆除。\n- **回合结算**:T 清空 CT、或 C4 爆炸 → T 胜;CT 清空 T(且未下包)、或成功拆包 → CT 胜。\n- 玩家操控的角色阵亡后,可切换到存活队友的视角 / 接管其操控(bot)。\n\n## 6. 5v5 AI 战场\n\n场上共 10 人:1 名玩家 + 4 名 AI 队友,对抗 5 名 AI 敌人,分属 CT / T 两阵营。AI 需要:能在地图内自主寻路巡逻、不卡墙;有视野索敌判定(被墙挡住看不见);遭遇敌方阵营自动开火交战;并参与 C4 的下包 / 拆包流程(进攻方推包安放、防守方前往拆除)。\n\n## 7. 操作与 HUD\n\n- 流畅的第一人称控制:WASD 移动、空格跳跃、指针锁定鼠标转视角。\n- 现代 2D UI 覆盖层:十字准星、生命值 / 护甲、动态弹药面板(开火递减、换弹恢复)、右上角实时击杀播报(Killfeed)。\n- **小地图(minimap)**:常驻显示 Dust2 俯视轮廓,实时标出自己、队友、被看见的敌人的点位,以及 C4 的位置。\n\n## 8. 声音\n\n游戏要有音效反馈。由于不使用外部音频文件,用 **Web Audio API 程序化合成** 声音即可。至少包含:不同武器的开火声(AK / M4 / AWP / 手枪要有区分)、换弹声、脚步声、AWP 开镜声、命中反馈音、C4 安放 / 拆除 / 爆炸声,以及击杀提示音。\n\n## 架构要求\n\n这是工业级挑战。你要独立完成技术选型、架构设计、数据结构封装,以及渲染循环、物理帧更新、React 状态生命周期三者之间的数据同步。全局状态引擎、武器系统、AI 状态机、3D 渲染与物理组件等关键系统都要真正实现,而非留空占位。",
|
||||
"en": "Build a browser-playable 3D first-person shooter prototype from scratch using **React + TypeScript + three.js**: a 5v5 battle recreating the core areas of Dust2. You may set up the project, install dependencies, run, debug and iterate until it is properly playable.\n\n## Deliverable\n\n- A standard **Vite + React 18 + TypeScript** project with a complete `package.json`, `vite.config.ts`, `index.html` and `src/`; `npm install && npm run dev` opens it to play.\n- **No external asset files whatsoever** (no 3D models, textures or audio). The map, characters and weapons are all generated procedurally from geometry in code, and sound is synthesized programmatically with Web Audio. The only third-party libraries are three / react / react-dom; pathfinding, state machines, physics and the render loop are all implemented yourself, with no off-the-shelf game engine.\n\n## 1. Dust2 map\n\nBuild the classic Dust2 areas at a sensible spatial scale in the 3D world, and you **must fully include**: T spawn, CT spawn, Long A, A site, Mid (with a passable set of Mid Doors), Catwalk, B tunnels and B site, connected to one another along Dust2's real routes. Include them all — do not submit a partial version missing areas. All walls and boxes have physical collision; neither players nor AI can pass through walls.\n\n## 2. Characters\n\nWith no external models, build **humanoid characters** in code: each must have a distinct head, torso, both arms and both legs — **do NOT cheat with a capsule or cylinder as a single column body**. The CT and T teams must be distinguishable at a glance; characters default to a weapon-holding pose and the weapon moves with the character's facing.\n\n## 3. Weapon system\n\nBuild an extensible weapon data system supporting switching between primary / secondary / melee, each with its own first-person held model. Reproduce at least these handling differences:\n\n- **AK-47**: high damage, heavy recoil after firing (pronounced crosshair spread + upward view kick).\n- **M4A4**: medium damage, low recoil, high fire rate.\n- **AWP**: one-shot lethal, very low fire rate, **right-click to scope** (3D zoom-in + a 2D scope overlay).\n- **Pistols**: at least Glock (T default), USP (CT default) and Deagle for pistol rounds; lower damage than rifles, Deagle in between with a small magazine.\n- **A melee knife**.\n\nHit detection uses **hitbox zones** (head / chest / stomach / arm / leg) with different damage each, and **headshot damage is twice body damage**. Armor reduces damage when present.\n\n## 4. Pistol round\n\nSupport a \"pistol round\" opening: everyone carries only their default pistol, no primary weapon, armor per pistol-round rules. This is the standard CS first round.\n\n## 5. C4 bomb and round win/loss\n\n- At round start the C4 is given to a T; when the carrier dies the bomb drops and can be picked up by another T.\n- **Plant**: a T carrying the bomb can plant the C4 within the A or B site, starting a countdown after planting.\n- **Defuse**: a CT standing at a planted C4 defuses it over a set time.\n- **Round resolution**: T wins by eliminating all CTs or the C4 exploding; CT wins by eliminating all Ts (with no plant) or by successfully defusing.\n- After your controlled character dies, you can switch to a living teammate's view / take over that bot.\n\n## 6. 5v5 AI battlefield\n\nTen players on the field: 1 human player + 4 AI teammates against 5 AI enemies, split into CT / T teams. The AI must: navigate and patrol the map autonomously without getting stuck on walls; have line-of-sight target detection (blocked by walls); automatically open fire on the enemy team on contact; and take part in the C4 plant / defuse flow (attackers push and plant, defenders go to defuse).\n\n## 7. Controls and HUD\n\n- Smooth first-person controls: WASD movement, space to jump, pointer-locked mouse to look.\n- A modern 2D UI overlay: crosshair, health / armor, a dynamic ammo panel (decrements on fire, restored on reload), and a live kill feed in the top-right.\n- A **minimap**: a persistent top-down Dust2 outline marking your own, teammates' and visible enemies' positions in real time, plus the C4's location.\n\n## 8. Sound\n\nThe game needs sound feedback. Since no external audio files are used, synthesize sound programmatically with the **Web Audio API**. Include at least: distinct firing sounds per weapon (AK / M4 / AWP / pistols differentiated), reload, footsteps, AWP scope-in, hit feedback, C4 plant / defuse / explosion, and a kill sound.\n\n## Architecture\n\nThis is an industrial-grade challenge. You own the technology choices, architecture, and data-structure design, plus the data sync between the render loop, the physics frame update, and the React state lifecycle. The global state engine, weapon system, AI state machine, and 3D-rendering and physics components must be genuinely implemented, not left as placeholders."
|
||||
},
|
||||
"tags": [
|
||||
"REACT",
|
||||
"3D",
|
||||
"THREE.JS",
|
||||
"GAME"
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"label": "Claude Fable 5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "High",
|
||||
"artifactDir": "claude-fable-5/claude-code-high",
|
||||
"order": 11,
|
||||
"runs": {
|
||||
"cs-dust2": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Fable 5(High 思考配额)生成的完整 Vite + React 18 + three.js 工程;本页为其构建产物(部署时由源码打包为单文件)。手动修复了一处开火 bug:模拟以固定 64Hz 步长推进,高刷屏(渲染帧率高于 64)下某一帧可能不推进任何 fixed step,原实现按帧写入并清零 fireTap 等 edge 触发输入,会在该帧把点击吞掉、导致偶发不开火;修复改为用 || 将这些输入锁存至被某个 fixed step 消费为止(src/game/engine.ts),使开火与刷新率无关。",
|
||||
"en": "A complete Vite + React 18 + three.js project generated by Claude Fable 5 (High effort) in Claude Code; this page is its build output (bundled from source into a single file at deploy time). One firing bug was fixed by hand: the simulation advances in fixed 64Hz steps, and on a high-refresh display (render fps above 64) a frame may run zero fixed steps, so the original per-frame assign-and-clear of edge-triggered inputs like fireTap could swallow a click on that frame and drop the shot; the fix latches those inputs with || until a fixed step consumes them (src/game/engine.ts), making firing refresh-rate-independent."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Claude Fable 5(High 思考档位)在 Claude Code 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「疾风圈地」。诊断:策略翻车,龟缩到极致:圈了 164 次却每次平均仅 3.9 格,勤劳画小圈、画一个被对手大圈盖一个,0 死 0 杀,终局仅 165 分。与 Opus xhigh 同病更甚——名叫疾风,啃得太碎。",
|
||||
"en": "One-shot Turf War agent by Claude Fable 5 (High effort) in Claude Code from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"疾风圈地\". Diagnosis: Strategic failure, turtling to the extreme: 164 captures averaging just 3.9 cells each — swift tiny loops instantly overwritten by the opponent's bigger ones, 0 deaths and 0 kills, ending at only 165. The same trap as Opus xhigh but worse — swift name, bites too small."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "疾风圈地"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"label": "Claude Fable 5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "xhigh",
|
||||
"artifactDir": "claude-fable-5/claude-code-xhigh",
|
||||
"order": 12.5,
|
||||
"runs": {
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Fable 5(xhigh 思考配额)一次生成。原始产出把表厂品牌写成 FABLE & CIE、机芯型号 CF-500,会在 Arena 盲评中暗示模型身份;本页仅替换为中性品牌与型号(AUREL & CIE / AC-500),其余未改。",
|
||||
"en": "Generated in one shot by Claude Fable 5 (xhigh effort) inside Claude Code. The original output branded the watch as FABLE & CIE with calibre CF-500, which would hint at the model identity in blind Arena mode; this page only replaces those with neutral branding (AUREL & CIE / AC-500), otherwise unmodified."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Claude Fable 5(xhigh 思考档位)在 Claude Code 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「画地为牢」。诊断:激进高产型——平均每次圈地约 42 格(全场最大),敢拖长尾巴换大地盘并主动切对手尾巴,自身几乎不送死,对 baseline 一度打出 1537:25。与同模型 high 档的龟缩(疾风圈地,平均仅 3.9 格)恰成反例:此模型思考档位越高反而越敢打。",
|
||||
"en": "One-shot Turf War agent by Claude Fable 5 (xhigh effort) in Claude Code from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"画地为牢\". Diagnosis: aggressive and high-output — captures average ~42 cells each (the largest of any agent), willing to trail far for big territory and to cut the opponent's trail, while almost never suiciding; it once beat a baseline 1537:25. The opposite of the same model's High run (疾风圈地, tiny 3.9-cell loops): for this model, more thinking makes it bolder, not more cautious."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "画地为牢"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"label": "Claude Fable 5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-fable-5/claude-code-max",
|
||||
"order": 12,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Fable 5(Max 思考配额)一次生成,本页为未经修改的原始产出。与「Claude 网页版」条目同模型、不同 Harness——两个标签页并排即可对比同一模型在不同运行环境下的差异。",
|
||||
"en": "Generated in one shot by Claude Fable 5 (Max effort) inside Claude Code; this page is the unmodified original output. Same model as the Claude Web App entry but a different harness — compare the two tabs to see how the environment shapes the result."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Fable 5(Max 思考配额)一次生成。",
|
||||
"en": "Generated in one shot by Claude Fable 5 (Max effort) inside Claude Code."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Claude Fable 5(Max 思考档位)在 Claude Code 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「地头蛇」。诊断:稳健强手——对 baseline 稳定取胜(872:512、1119:307),圈地量足。同为 Fable 5,与 high 档的龟缩(疾风圈地)差距明显,再次印证此模型思考档位越高越敢打。",
|
||||
"en": "One-shot Turf War agent by Claude Fable 5 (Max effort) in Claude Code from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"地头蛇\". Diagnosis: a solid strong performer — beats baselines consistently (872:512, 1119:307) with healthy capture volume. Same model as the High turtle (疾风圈地) yet far stronger, again showing that for Fable 5 more thinking means bolder play."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "地头蛇"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Claude Fable 5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Cursor",
|
||||
"effort": "High",
|
||||
"artifactDir": "claude-fable-5/cursor-high",
|
||||
"order": 13,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Claude Fable 5(High 思考配额)作为子 agent 一次生成,本页为未经修改的原始产出。与「Claude 网页版」「Claude Code」条目同模型、不同 Harness,可并排对比运行环境对产出的影响。",
|
||||
"en": "Generated in one shot by a Claude Fable 5 (High effort) subagent inside Cursor; this page is the unmodified original output. Same model as the Claude Web App and Claude Code entries but a different harness — compare them side by side to see how the environment shapes the result."
|
||||
},
|
||||
"contributor": "Mist-wu"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Claude Fable 5(High 思考配额)作为子 agent 一次生成。",
|
||||
"en": "Generated in one shot by a Claude Fable 5 (High effort) subagent inside Cursor."
|
||||
},
|
||||
"contributor": "Mist-wu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Claude Fable 5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Web App",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-fable-5/claude-web-app-max",
|
||||
"order": 10,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude 网页版以 Fable 5 Max(最高思考强度)运行,过程中触发一次 auto compact 与工具调用上限,手动输入「继续」后完成;ID 水印与英文支持为事后在 Cursor 中用 Composer 2.5 Fast 一条提示词补加,其余均为一次生成。已知小问题:开局可能卡在一棵树里,按一下 G 即可脱身。",
|
||||
"en": "Run in the Claude web app on Fable 5 Max (max thinking effort); hit one auto-compact and the tool-call limit mid-run, finished after a manual \"continue\". The ID watermark and English support were patched in afterwards with a single prompt via Composer 2.5 Fast in Cursor; everything else is one-shot. Known quirk: you may spawn stuck inside a tree - press G once to get free."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude 网页版以 Fable 5 Max(最高思考强度)一次生成。",
|
||||
"en": "Generated in one shot in the Claude web app on Fable 5 Max (max thinking effort)."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Claude Haiku 4.5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Default",
|
||||
"artifactDir": "claude-haiku-4-5/claude-code-default",
|
||||
"order": 19.5,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Haiku 4.5(Default 思考配额)一次生成。原始 HTML 因 Three.js 异步加载、渲染 canvas 绑定和 world 变量接线问题无法正常打开;Codex 仅修复启动接线以让原始实现可运行,未改动玩法、地形、视觉设计或控制逻辑。",
|
||||
"en": "Generated in one shot by Claude Haiku 4.5 (Default effort) inside Claude Code. The original HTML could not open correctly due to Three.js async loading, render-canvas binding, and world variable wiring issues; Codex only fixed the startup wiring so the original implementation can run, without changing gameplay, terrain, visual design, or control logic."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Haiku 4.5(Default 思考配额)一次生成;本页为未经修改的原始 SVG 产出。",
|
||||
"en": "Generated in one shot by Claude Haiku 4.5 (Default effort) inside Claude Code; unmodified original SVG output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Claude Opus 4.5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Cursor",
|
||||
"effort": "Thinking",
|
||||
"artifactDir": "claude-opus-4-5/cursor-thinking",
|
||||
"order": 17,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 Claude Opus 4.5(Thinking 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.5 (Thinking effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 Claude Opus 4.5(Thinking 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.5 (Thinking effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Claude Opus 4.6",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-opus-4-6/claude-code-max",
|
||||
"order": 16,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.6(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.6 (Max effort) inside Claude Code; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.6(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.6 (Max effort) inside Claude Code; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Claude Opus 4.6",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Cursor",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-opus-4-6/cursor-max",
|
||||
"order": 16.5,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 Claude Opus 4.6(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.6 (Max effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 Claude Opus 4.6(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.6 (Max effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "Claude Opus 4.7",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-opus-4-7/claude-code-max",
|
||||
"order": 15,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.7(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.7 (Max effort) inside Claude Code; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.7(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.7 (Max effort) inside Claude Code; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.7(Max 思考配额)一次生成;本页为未经修改的原始产出。已知 bug(未修复):点击「Exploded View」分解视图几乎没有反应,零件始终保持组装状态。位置在 animate()(933–934 行):先 const t = clock.getElapsedTime() 再 const dt = Math.min(clock.getDelta(), 0.05)。THREE.Clock 的 getElapsedTime() 内部已经调用过一次 getDelta() 并把 oldTime 重置为当前时刻,因此紧随其后的 getDelta() 返回的是这两行之间的间隔,几乎为 0,导致每帧 dt≈0。分解动画靠 explodeAmount += (explodeTarget - explodeAmount) * min(1, dt*4) 推进,dt≈0 时几乎不增长(实测点击后 2 秒仅到 0.0008,需数分钟才能展开),所以看起来失效;水晶玻璃位移同理。齿轮转动用的是 t 不受影响,机芯照常运转。",
|
||||
"en": "Generated in one shot by Claude Opus 4.7 (Max effort) inside Claude Code; unmodified original output. Known bug (not fixed): clicking 'Exploded View' does almost nothing — the parts stay assembled. Located in animate() (lines 933-934): const t = clock.getElapsedTime() followed by const dt = Math.min(clock.getDelta(), 0.05). THREE.Clock.getElapsedTime() internally already calls getDelta() and resets oldTime to now, so the getDelta() on the next line returns the gap between those two lines — effectively 0 — making dt≈0 every frame. The explode is driven by explodeAmount += (explodeTarget - explodeAmount) * min(1, dt*4), which barely advances when dt≈0 (measured ~0.0008 two seconds after the click; it would take minutes to open), so it looks broken; the crystal offset fails the same way. The gear rotations use t directly and are unaffected, so the movement keeps running."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Claude Opus 4.7",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Cursor",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-opus-4-7/cursor-max",
|
||||
"order": 15.5,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Claude Opus 4.7(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.7 (Max effort) inside Cursor; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Claude Opus 4.7(Max 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.7 (Max effort) inside Cursor; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Claude Opus 4.8",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "xhigh",
|
||||
"order": 14.1,
|
||||
"runs": {
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Claude Opus 4.8(xhigh 思考档位)在 Claude Code 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「蚕食」。诊断:策略翻车,过度保守:全程 0 死亡,但每次只圈 7 格左右的小块,圈来的地被对手的大圈反复覆盖,累计吃 776 格终局只剩 277。撞上提示词警告的失败模式——只求不败拿不到高排名。",
|
||||
"en": "One-shot Turf War agent by Claude Opus 4.8 (xhigh effort) in Claude Code from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"蚕食\". Diagnosis: Strategic failure, over-cautious: 0 deaths all match, but each capture is a tiny ~7-cell loop that the opponent's bigger loops keep overwriting — it gains 776 cells across the match yet ends with 277. Exactly the failure the prompt warns about: merely avoiding losing does not rank."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "蚕食"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"label": "Claude Opus 4.8",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-opus-4-8/claude-code-max",
|
||||
"order": 14,
|
||||
"runs": {
|
||||
"mythos-craft": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.8(Max 思考配额)一次生成,本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Claude Opus 4.8 (Max effort) inside Claude Code; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "mythos-craft-2.html",
|
||||
"note": {
|
||||
"zh": "第二版:在 Claude Code 中由 Claude Opus 4.8(Max 思考配额)一次生成。基于 Three.js 的体素世界,含柏林噪声地形、第一人称 WASD + 鼠标视角、DDA 体素射线拾取的破坏/放置、程序化贴图图集,并自带大理石神庙、水晶尖塔与悬浮岛。代码由模型直接写出,仅做了一次 JS 语法检查,未做其他修改。",
|
||||
"en": "Second version: generated in one shot by Claude Opus 4.8 (Max effort) inside Claude Code. A Three.js voxel world with Perlin-noise terrain, first-person WASD + mouse-look, DDA voxel-raycast break/place, a procedural texture atlas, and a built-in marble temple, crystal spires and a floating sky-isle. Code written directly by the model; only a JS syntax check was run, otherwise unmodified."
|
||||
},
|
||||
"contributor": "OHMFOHS"
|
||||
}
|
||||
],
|
||||
"pelican-cycling": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.8(Max 思考配额)一次生成。",
|
||||
"en": "Generated in one shot by Claude Opus 4.8 (Max effort) inside Claude Code."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"note": {
|
||||
"zh": "第二版:在 Claude Code 中由 Claude Opus 4.8(Max 思考配额)一次手写的 SVG,未经修改。海边日落场景:渐变天空与海面、沙滩、远处帆船与海鸥,一只鹈鹕骑着红色自行车,并以速度线增添动感。",
|
||||
"en": "Second version: a hand-authored SVG written in one shot by Claude Opus 4.8 (Max effort) inside Claude Code; unmodified. A seaside sunset scene — gradient sky and sea, beach, a distant sailboat and gull, and a pelican riding a red bicycle, with motion lines for liveliness."
|
||||
},
|
||||
"contributor": "OHMFOHS"
|
||||
}
|
||||
],
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.8(Max 思考配额)一次生成,本页为未经修改的原始产出。基于 Three.js 的交互式 3D 镂空机械表:可旋转、切换爆炸视图、零件标签高亮与机芯信息面板,含擒纵机构、摆轮等机芯结构。",
|
||||
"en": "Generated in one shot by Claude Opus 4.8 (Max effort) inside Claude Code; unmodified original output. An interactive 3D skeleton mechanical watch on Three.js — orbit rotation, exploded view, highlighted part labels and a movement info panel, with escapement, balance wheel and other movement parts."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"cs-dust2": {
|
||||
"file": "dust2-arena",
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Opus 4.8(Max 思考配额)一次生成的完整 Vite + React 18 + three.js 工程,未经修改;本页为其构建产物(部署时由源码打包为单文件)。与 Claude Fable 5 条目同一道 cs-dust2 题、不同模型——可对比同题下两个模型的工程实现。",
|
||||
"en": "A complete Vite + React 18 + three.js project generated in one shot by Claude Opus 4.8 (Max effort) in Claude Code, unmodified; this page is its build output (bundled from source into a single file at deploy time). Same cs-dust2 case as the Claude Fable 5 entry but a different model — compare how the two implement the same task."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Claude Opus 4.8(Max 思考档位)在 Claude Code 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则,策略文件不公开、仅注册运行,由维护者亲自执行。自命名「圈套」。",
|
||||
"en": "One-shot Turf War agent by Claude Opus 4.8 (Max effort) in Claude Code from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished; this entry registers the maintainer-executed run. Self-named \"圈套\"."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "圈套"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Claude Sonnet 4.6",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "claude-sonnet-4-6/claude-code-max",
|
||||
"order": 18.9,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Claude Sonnet 4.6(Max 思考配额)一次生成。",
|
||||
"en": "Generated in one shot by Claude Sonnet 4.6 (Max effort) inside Claude Code."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"label": "Claude Sonnet 4.6",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Cursor",
|
||||
"effort": "High",
|
||||
"artifactDir": "claude-sonnet-4-6/cursor-high",
|
||||
"order": 19,
|
||||
"runs": {
|
||||
"pelican-cycling": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 Claude Sonnet 4.6(High 思考配额)一次生成。原始 SVG 的注释中混入非法 XML/编码字节,导致浏览器只渲染到一半并显示解析错误;Codex 仅将这些注释里的坏字符替换为 ASCII 连字符以修复加载错误,未改动任何图形元素。",
|
||||
"en": "Generated in one shot by Claude Sonnet 4.6 (High effort) inside Cursor CLI. The original SVG contained invalid XML/encoding bytes inside comments, causing browsers to stop rendering partway through with a parse error; Codex only replaced those bad comment characters with ASCII hyphens to fix loading, without changing any graphical elements."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"note": {
|
||||
"zh": "第二版:在 Cursor CLI 中以 Claude Sonnet 4.6(High 思考配额)一次生成,作为独立 SVG 变体登记,保留原版产物不变。",
|
||||
"en": "Second version: generated in one shot by Claude Sonnet 4.6 (High effort) inside Cursor CLI and registered as a separate SVG variant, preserving the original artifact."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Claude Sonnet 5",
|
||||
"vendor": "Anthropic",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"order": 12.5,
|
||||
"runs": {
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Claude Sonnet 5(Max 思考档位)在 Claude Code 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「卷地风」。诊断:全程 0 次成功圈地,反复送死 11 次——离开领地后撞上自己的尾巴/岩石/边界而亡,一个闭环都没画成。属于莽夫送死型:太急、路径规划没避开自身尾巴。",
|
||||
"en": "One-shot Turf War agent by Claude Sonnet 5 (Max effort) in Claude Code from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"卷地风\". Diagnosis: Zero completed captures and 11 self-inflicted deaths — it leaves home and dies on its own trail / a rock / the border before ever closing a loop. A reckless death-loop: too eager, its pathing never avoids its own trail."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "卷地风"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Composer 2.5",
|
||||
"vendor": "Cursor",
|
||||
"harness": "Cursor",
|
||||
"effort": "Default",
|
||||
"artifactDir": "composer-2-5/cursor-default",
|
||||
"order": 71,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Composer 2.5 一次生成,本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Composer 2.5 inside Cursor; this page is the unmodified original output."
|
||||
},
|
||||
"contributor": "Mist-wu"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Composer 2.5 一次生成。",
|
||||
"en": "Generated in one shot by Composer 2.5 inside Cursor."
|
||||
},
|
||||
"contributor": "Mist-wu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Composer 2.5",
|
||||
"vendor": "Cursor",
|
||||
"harness": "Grok Build TUI",
|
||||
"effort": "Default",
|
||||
"artifactDir": "composer-2-5/grok-build-tui-default",
|
||||
"order": 72,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Grok Build TUI 中以 Composer 2.5 生成;本页为修复版(改 1 行)。问题:WOOD(原木)方块只定义了 top 与 side 两个面、没有 bottom,而图集构建 buildAtlas 会为每个方块依次取 top/side/bottom 三面;取 WOOD 底面时 getFaceImage 读 t.bottom.image,t.bottom 为 undefined 直接抛 TypeError。该调用在 initTextures() 顶层执行,异常令整段初始化中断,'进入世界'按钮的点击监听还没绑定——表现为点击开始按钮毫无反应、进不去世界。修复:getFaceImage 在某面缺失时回退到已有的面(底面回退到顶面,正好是原木年轮端面的正确外观),其余原样。",
|
||||
"en": "Generated with Composer 2.5 in the Grok Build TUI; fixed build (1 line changed). Bug: the WOOD block defines only top and side faces, no bottom, but the atlas builder requests top/side/bottom for every block; fetching WOOD's bottom hit getFaceImage reading t.bottom.image where t.bottom is undefined, throwing a TypeError. That call runs at the top level of initTextures(), so the exception aborted the whole init before the 'enter world' click listener was ever attached — clicking the start button did nothing and you could not enter the world. Fix: getFaceImage now falls back to an existing face when one is missing (bottom falls back to top, which is also the correct ring end-grain for a log); nothing else changed."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Grok Build TUI 中以 Composer 2.5 生成,本页为未经修改的原始产出。",
|
||||
"en": "Generated with Composer 2.5 in the Grok Build TUI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Composer 2.5",
|
||||
"vendor": "Cursor",
|
||||
"harness": "Cursor",
|
||||
"effort": "Max",
|
||||
"artifactDir": "composer-2-5/cursor-max",
|
||||
"order": 70,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Composer 2.5 生成;本页为修复版(改 1 行)+ 补一个小功能。问题:第 1152 行水平速度先乘 speed*dt,位移时又乘一次 dt,实际步速只有设计值(5 格/秒)的约 1/60。修复前:走路如蜗牛、跳跃下落却正常;沙漠雪地等群系与神话建筑实际走不到,体感像「切换不了场景」——游戏本无传送功能,群系需步行抵达。原因:X/Z 轴把「每帧位移」当「每秒速度」存入 velocity,与 Y 轴语义不一致。去掉一次 dt 后步行恢复 5 格/秒。另外它确实生成了精灵螺旋塔、龙神殿、天空遗迹三处景点(也画在小地图上),但原版只能徒步抵达——已补加按 G 依次瞬移到三处景点的功能,开屏说明里也加了一行。",
|
||||
"en": "Generated with Composer 2.5 in Cursor; fixed build (1 line changed) plus one small addition. Bug: line 1152 multiplied horizontal velocity by speed*dt, then displacement multiplied by dt again, leaving walking at ~1/60 of the designed 5 blocks/s. Before the fix: walking crawled while jumping/falling felt normal, and biomes or mythic structures were virtually unreachable — which read as \"scene switching is broken\" (there is no teleport; biomes are reached on foot). Cause: X/Z stored per-frame displacement in a per-second velocity field, inconsistent with the Y axis. With one dt removed walking is back to 5 blocks/s. It does generate three landmarks (Elven Spiral Tower, Dragon Temple, Sky Ruins — also drawn on the minimap) that were only reachable on foot, so a G-key cycle-teleport to them was added, with a line in the start screen."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "DeepSeek V4 Flash",
|
||||
"vendor": "DeepSeek",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "deepseek-v4-flash/claude-code-max",
|
||||
"order": 50,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 DeepSeek V4 Flash(Max)一次生成,未修改。你是一个顶级的资深游戏开发者。请为我使用单 HTML5 和 Canvas(或 Three.js)编写一个《我的世界》克隆版。请包含以下核心功能:1. 一个基于柏林噪声(Perlin Noise)生成的无限或可滚动的 3D 地形。2. 第一人称视角,支持使用 WASD 移动和鼠标环视。3. 支持至少 3 种方块类型(如:泥土、草地、石头等),并支持鼠标左键破坏方块,右键放置方块。材质要尽可能还原。4. 要有一个内置的、极其丰富的神话幻想中的建筑和地形等,由你自行设计。",
|
||||
"en": "One-shot by DeepSeek V4 Flash (Max effort) in Claude Code, unmodified. You are a top-tier veteran game developer. Build me a Minecraft clone using a single HTML5 file and Canvas (or Three.js). Include the following core features:1. An infinite or scrollable 3D terrain generated from Perlin noise.2. A first-person view with WASD movement and mouse look.3. At least 3 block types (e.g. dirt, grass, stone), with left-click to break and right-click to place blocks. Keep the textures as faithful as possible.4. A built-in, extremely rich set of mythic-fantasy buildings and terrain, designed at your own discretion."
|
||||
},
|
||||
"contributor": "Av1dity"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "DeepSeek V4 Flash",
|
||||
"vendor": "DeepSeek",
|
||||
"harness": "Reasonix",
|
||||
"effort": "Max",
|
||||
"artifactDir": "deepseek-v4-flash/reasonix-max",
|
||||
"order": 90.5,
|
||||
"runs": {
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Reasonix 编程代理环境中一次生成。使用 Three.js (CDN) 构建了完整的 3D 镂空机械表,包含摆轮、游丝、擒纵机构、齿轮系、发条盒、红宝石轴承、夹板等全部可见机芯结构,支持 OrbitControls 旋转、爆炸视图、CSS2D 零件标签和交互信息面板。一次生成,无人工修复。",
|
||||
"en": "Generated in one shot in the Reasonix coding agent environment. Built a complete interactive 3D skeleton mechanical watch with Three.js (CDN), including balance wheel, hairspring, escapement, gear train, mainspring barrel, ruby jewels, bridges and all visible movement components. Features OrbitControls rotation, exploded view, CSS2D part labels, and interactive info panel. One-shot generation, no manual fixes."
|
||||
},
|
||||
"contributor": "6bcj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "DeepSeek-V4-Pro",
|
||||
"vendor": "DeepSeek",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "deepseek-v4-pro/claude-code-max",
|
||||
"order": 90,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 DeepSeek V4 Pro(最高思考强度)一次生成,未修复。包含 Perlin 噪声地形、WASD + 鼠标第一人称操控、12 种方块类型(含水、黑曜石、水晶、金块等)、程序化材质纹理、四种神话建筑(水晶塔、石环阵、龙骨遗迹、天空遗迹),按 G 可随机传送至神话建筑。",
|
||||
"en": "Generated in one shot via Claude Code with DeepSeek V4 Pro (max thinking effort), no fixes. Features Perlin noise terrain, WASD + mouse first-person controls, 12 block types (including water, obsidian, crystal, gold), procedural textures, and four mythic structures (Crystal Tower, Stone Circle, Dragon Skeleton, Sky Ruins). Press G to teleport between landmarks."
|
||||
},
|
||||
"contributor": "Mintcolour"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 DeepSeek V4 Pro(最高思考强度)一次生成。场景包含天空、海洋、沙滩、棕榈树、海鸥、完整自行车细节(辐条、链条、齿轮)以及鹈鹕(长喙、喉囊、羽翼、踩踏动作)。",
|
||||
"en": "Generated in one shot via Claude Code with DeepSeek V4 Pro (max thinking effort). Scene includes sky, ocean, beach, palm trees, seagulls, detailed bicycle (spokes, chain, gears), and a pelican (beak, gular pouch, wings, pedaling pose)."
|
||||
},
|
||||
"contributor": "Mintcolour"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 DeepSeek-V4-Pro(最高思考强度)一次生成,未进行任何修改。",
|
||||
"en": "Generated in one shot by DeepSeek-V4-Pro inside Claude Code with Max effort, unmodified."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Doubao Seed 2.0 Mini",
|
||||
"vendor": "ByteDance",
|
||||
"harness": "Doubao Web",
|
||||
"effort": "Fast Mode",
|
||||
"artifactDir": "doubao-seed-2-0-mini/doubao-web-fast-mode",
|
||||
"order": 96,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在豆包网页版以快速模式一次生成。\n\n「要不要我再给你做一个带简单动画(翅膀扇动、车轮转动)的增强版 SVG 代码?」",
|
||||
"en": "Generated in one shot on Doubao web app in Lite mode.\n\n\"Should I make you an enhanced version of the SVG code with simple animations (flapping wings, rotating wheels)?\""
|
||||
},
|
||||
"contributor": "LpIuma"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Doubao Seed 2.0 Pro",
|
||||
"vendor": "ByteDance",
|
||||
"harness": "Doubao Web",
|
||||
"effort": "Pro Mode",
|
||||
"artifactDir": "doubao-seed-2-0-pro/doubao-web-pro-mode",
|
||||
"order": 95,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "使用豆包网页版超能模式,首次输出了 png 图片,经提醒后生成 svg 代码。",
|
||||
"en": "Using Doubao web app (Pro mode), it initially output a PNG image, then generated the SVG code after being reminded."
|
||||
},
|
||||
"contributor": "LpIuma"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Gemini 3.1 Pro",
|
||||
"vendor": "Google",
|
||||
"harness": "Google AI Studio",
|
||||
"effort": "High",
|
||||
"artifactDir": "gemini-3-1-pro/google-ai-studio-high",
|
||||
"order": 42,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Google AI Studio 以 Gemini 3.1 Pro Preview High(最高思考强度)一次生成。",
|
||||
"en": "Generated in Google AI Studio on Gemini 3.1 Pro Preview High (max thinking effort) in one shot."
|
||||
},
|
||||
"contributor": "LpIuma"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Google AI Studio 以 Gemini 3.1 Pro Preview High(最高思考强度)一次生成,未经人工修改。",
|
||||
"en": "Generated in Google AI Studio on Gemini 3.1 Pro Preview High (max thinking effort) in one shot, unmodified."
|
||||
},
|
||||
"contributor": "colorful-glassblock"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Gemini 3.1 Pro",
|
||||
"vendor": "Google",
|
||||
"harness": "Cursor",
|
||||
"effort": "Default",
|
||||
"artifactDir": "gemini-3-1-pro/cursor-default",
|
||||
"order": 41,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Gemini 3.1 Pro 默认设置一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Gemini 3.1 Pro inside Cursor with default settings; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Gemini 3.1 Pro 默认设置一次生成;修复了 SVG 注释中的非法 UTF-8 字节,未修改绘图元素。",
|
||||
"en": "Generated in one shot by Gemini 3.1 Pro inside Cursor with default settings; fixed invalid UTF-8 bytes in SVG comments without changing drawing elements."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "Gemini 3.1 Pro",
|
||||
"vendor": "Google",
|
||||
"harness": "Gemini Web",
|
||||
"effort": "Deep Think",
|
||||
"artifactDir": "gemini-3-1-pro/gemini-web-deepthink",
|
||||
"order": 41,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Gemini Web 中以 Gemini 3.1 Pro(Deep Think)一次生成,未修改。",
|
||||
"en": "One-shot by Gemini 3.1 Pro (Deep Think) in Gemini Web, unmodified."
|
||||
},
|
||||
"contributor": "lupan-lu"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Gemini Web 中以 Gemini 3.1 Pro(Deep Think)一次生成,未修改。",
|
||||
"en": "One-shot by Gemini 3.1 Pro (Deep Think) in Gemini Web, unmodified."
|
||||
},
|
||||
"contributor": "lupan-lu"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Gemini Web 中以 Gemini 3.1 Pro(Deep Think)一次生成,未修改。",
|
||||
"en": "One-shot by Gemini 3.1 Pro (Deep Think) in Gemini Web, unmodified."
|
||||
},
|
||||
"contributor": "lupan-lu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"label": "Gemini 3.1 Pro",
|
||||
"vendor": "Google",
|
||||
"harness": "AntiGravity",
|
||||
"effort": "High",
|
||||
"artifactDir": "gemini-3-1-pro/antigravity-high",
|
||||
"order": 40,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "通过 AntiGravity 生成(thinking effort: high);一次生成,未发现需要修复的问题,原样展示。",
|
||||
"en": "Generated via AntiGravity (thinking effort: high); one shot, no fixes needed — shown as-is."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"note": {
|
||||
"zh": "由社区 PR #3(Codex 工作流)提交,提交者未注明运行环境与思考档位。",
|
||||
"en": "Contributed via community PR #3 (a Codex workflow); the submitter did not specify the runtime or effort level."
|
||||
},
|
||||
"contributor": "XA2333"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "通过 AntiGravity 生成(thinking effort: high);一次生成,未进行任何修改。",
|
||||
"en": "Generated via AntiGravity (thinking effort: high); one shot, unmodified."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
},
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Gemini 3.1 Pro(High 思考档位)在 AntiGravity 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「寒冰王座」。诊断:中游水平,与 baseline 五五开(一胜一负、比分咬得很紧),能正常圈地、不崩、也不龟缩,但缺乏 Opus max 那种大环+主动击杀的压制力。",
|
||||
"en": "One-shot Turf War agent by Gemini 3.1 Pro (High effort) in AntiGravity from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"寒冰王座\". Diagnosis: mid-tier — roughly even with the baseline (one win, one loss, both close); it captures normally, neither crashes nor turtles, but lacks the big-loop-plus-active-kill dominance of Opus max."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "寒冰王座"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Gemini 3.5 Flash",
|
||||
"vendor": "Google",
|
||||
"harness": "Google AI Studio",
|
||||
"effort": "High",
|
||||
"artifactDir": "gemini-3-5-flash/google-ai-studio-high",
|
||||
"order": 52,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Google AI Studio 以 Gemini 3.5 Flash High(最高思考强度)一次生成。",
|
||||
"en": "Generated in Google AI Studio on Gemini 3.5 Flash High (max thinking effort) in one shot."
|
||||
},
|
||||
"contributor": "LpIuma"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Gemini 3.5 Flash",
|
||||
"vendor": "Google",
|
||||
"harness": "Cursor",
|
||||
"effort": "Default",
|
||||
"artifactDir": "gemini-3-5-flash/cursor-default",
|
||||
"order": 51,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Gemini 3.5 Flash 默认设置一次生成。本页为未经修改的原始产出。包含基于柏林噪声的无限三维体素地形,自动爬坡(Step Climb)物理碰撞,Web Audio API 合成的风声/方块破坏/放置/跳跃音效及五重和弦背景合成乐。内置了世界之树、天空太阳神殿、黑曜石火山龙巢、悬浮精灵柱四处规模宏大、细节饱满的神话遗迹,并配备一键折跃与二维小地图罗盘导航。",
|
||||
"en": "Generated in one shot by Gemini 3.5 Flash inside Cursor with default settings. Unmodified raw output featuring an infinite 3D voxel terrain driven by Perlin noise, responsive step-climbing collision physics, and synthesized sound effects (block breaking, placing, jumping) alongside a 5-chord ambient synth soundtrack via Web Audio API. Built-in with four massive, highly detailed mythical landmarks — Yggdrasil, the Aether Sun Temple, the Obsidian Volcano Keep, and the Floating Elven Spires — complete with instant travel Codex and 2D minimap compass navigation."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor 中以 Gemini 3.5 Flash 默认设置一次生成。本页为原始未经修改的产出,具有极高精细度的矢量细节、生动的动画感构图以及富有趣味性的画面彩蛋(如鹈鹕喉囊里的小鱼)。",
|
||||
"en": "Generated in one shot by Gemini 3.5 Flash inside Cursor with default settings. Unmodified raw output featuring extremely rich vector details, dynamic composition, and delightful visual easter eggs like the swallowed fish in the gular pouch."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"label": "Gemini 3.5 Flash",
|
||||
"vendor": "Google",
|
||||
"harness": "AntiGravity",
|
||||
"effort": "High",
|
||||
"artifactDir": "gemini-3-5-flash/antigravity-high",
|
||||
"order": 50,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "通过 AntiGravity 生成(thinking effort: high);本页为修复版(仅改 1 行)。问题:16 张贴图按「上为原点」的行序画进 4×4 图集,UV 坐标表也按同一行序编写,但 CanvasTexture 默认 flipY=true 把采样行上下镜像(第 r 行实际采到第 3-r 行)。修复前:草地顶面显示成紫水晶、侧面采到带透明孔洞的传送门/树叶贴图,配合 alphaTest 出现大量「透明方块」,放置的方块贴图与名称完全对不上。原因:flipY 与图集行序约定不一致。加上 flipY=false 后行序对齐,地形与方块全部正常。",
|
||||
"en": "Generated via AntiGravity (thinking effort: high); fixed build (1 line changed). Bug: 16 tiles drawn into a 4x4 atlas with top-origin rows, and the UV table uses the same convention, but CanvasTexture's default flipY=true mirrors the rows on sampling (row r actually reads row 3-r). Before the fix: grass tops rendered as purple crystal, sides sampled the portal/leaves tiles with alpha holes — hence the many \"transparent blocks\" — and placed blocks never matched their names. Cause: flipY disagreeing with the atlas row convention. With flipY=false everything lines up."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"note": {
|
||||
"zh": "由社区 PR #3(Codex 工作流)提交,提交者未注明运行环境与思考档位。",
|
||||
"en": "Contributed via community PR #3 (a Codex workflow); the submitter did not specify the runtime or effort level."
|
||||
},
|
||||
"contributor": "XA2333"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "通过 AntiGravity 生成(thinking effort: high);一次生成,未进行任何修改。",
|
||||
"en": "Generated via AntiGravity (thinking effort: high); one shot, unmodified."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "GLM-5.1",
|
||||
"vendor": "Zhipu AI",
|
||||
"harness": "ZCode",
|
||||
"effort": "Max",
|
||||
"artifactDir": "glm-5-1/zcode-max",
|
||||
"order": 120,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在智谱清言网页版 GLM-5.1、使用 cases.json 中 pelican-cycling 的提示词逐字输入,一次生成,产物为独立 SVG 文件,无任何修复。",
|
||||
"en": "In Zhipu Qingyan web app GLM-5.1, using the pelican-cycling prompt from cases.json verbatim, one-shot generation, output as a standalone SVG file, no fixes."
|
||||
},
|
||||
"contributor": "Dmt3tianOVO"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"label": "GLM-5.2",
|
||||
"vendor": "Zhipu AI",
|
||||
"harness": "ZCode",
|
||||
"effort": "Max",
|
||||
"artifactDir": "glm-5-2/zcode-max",
|
||||
"order": 119,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 ZCode 中以 GLM-5.2(Max 思考配额)一次生成。原始 HTML 因热栏引用了未定义的 GLASS 常量而无法进入;Codex 仅将该引用修正为已有的 CRYSTAL 方块,未改动玩法或视觉设计。",
|
||||
"en": "Generated in one shot by GLM-5.2 (Max effort) inside ZCode. The original HTML could not enter the game because the hotbar referenced an undefined GLASS constant; Codex only corrected that reference to the existing CRYSTAL block, without changing gameplay or visual design."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 ZCode 中以 GLM-5.2(Max 思考配额)一次生成;本页为未经修改的原始 SVG 产出。",
|
||||
"en": "Generated in one shot by GLM-5.2 (Max effort) inside ZCode; unmodified original SVG output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"note": {
|
||||
"zh": "在 ZCode 中以 GLM-5.2(Max 思考配额)一次生成的第二个 SVG 版本;本页为未经修改的原始 SVG 产出。",
|
||||
"en": "Second SVG variant generated in one shot by GLM-5.2 (Max effort) inside ZCode; unmodified original SVG output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
],
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 ZCode 中以 GLM-5.2(Max 思考配额)一次生成,未进行任何修改。",
|
||||
"en": "Generated in one shot by GLM-5.2 (Max effort) inside ZCode, unmodified."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"label": "GLM-5 Turbo",
|
||||
"vendor": "Zhipu AI",
|
||||
"harness": "ZCode",
|
||||
"effort": "Thinking",
|
||||
"artifactDir": "glm-5-turbo/zcode-thinking",
|
||||
"order": 121,
|
||||
"runs": {
|
||||
"mythos-craft": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 ZCode 中以 GLM-5 Turbo(Thinking 思考配额)一次生成。原始 HTML 因脚本括号错误、缺少 Game 初始化和缺少收尾标签而无法进入;Codex 仅修复这些启动接线问题,未改动玩法或视觉设计。",
|
||||
"en": "Generated in one shot by GLM-5 Turbo (Thinking effort) inside ZCode. The original HTML could not enter the game because of script-brace errors, missing Game initialization, and missing closing tags; Codex only fixed those startup wiring issues, without changing gameplay or visual design."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "mythos-craft-2.html",
|
||||
"note": {
|
||||
"zh": "在 ZCode 中以 GLM-5 Turbo(Thinking 思考配额)一次生成的第二个 HTML 版本。原始 HTML 因雾效参数在 shader 编译后才初始化、且点击事件被 loading 覆盖层挡住而无法进入;Codex 仅修复这些启动/加载问题,未改动玩法或视觉设计。",
|
||||
"en": "Second HTML variant generated in one shot by GLM-5 Turbo (Thinking effort) inside ZCode. The original HTML could not enter because fog parameters were initialized after shader compilation and the loading overlay blocked the click handler; Codex only fixed those startup/loading issues, without changing gameplay or visual design."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
],
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 ZCode 中以 GLM-5 Turbo(Thinking 思考配额)一次生成;本页为未经修改的原始 SVG 产出。",
|
||||
"en": "Generated in one shot by GLM-5 Turbo (Thinking effort) inside ZCode; unmodified original SVG output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.2",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Cursor",
|
||||
"effort": "xhigh",
|
||||
"artifactDir": "gpt-5-2/cursor-xhigh",
|
||||
"order": 33,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 GPT-5.2(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.2 (xhigh effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 GPT-5.2(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.2 (xhigh effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.3 Codex",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Cursor",
|
||||
"effort": "xhigh",
|
||||
"artifactDir": "gpt-5-3-codex/cursor-xhigh",
|
||||
"order": 32,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 GPT-5.3 Codex(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.3 Codex (xhigh effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 GPT-5.3 Codex(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.3 Codex (xhigh effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.4",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Cursor",
|
||||
"effort": "xhigh",
|
||||
"artifactDir": "gpt-5-4/cursor-xhigh",
|
||||
"order": 31,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 GPT-5.4(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.4 (xhigh effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 GPT-5.4(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.4 (xhigh effort) inside Cursor CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.4",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Codex CLI",
|
||||
"effort": "xhigh",
|
||||
"artifactDir": "gpt-5-4/codex-xhigh",
|
||||
"order": 31.5,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Codex CLI 中以 GPT-5.4(xhigh 思考配额)生成并整理为单文件 HTML 产物。",
|
||||
"en": "Generated by GPT-5.4 (xhigh effort) inside Codex CLI and packaged as a single-file HTML artifact."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Codex CLI 中以 GPT-5.4(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.4 (xhigh effort) inside Codex CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"note": {
|
||||
"zh": "在 Codex CLI 中以 GPT-5.4(xhigh 思考配额)一次生成的第二个 SVG 版本;本页为未经修改的原始产出。",
|
||||
"en": "Second SVG variant generated in one shot by GPT-5.4 (xhigh effort) inside Codex CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
],
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Codex CLI 中以 GPT-5.4(xhigh 思考配额)一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by GPT-5.4 (xhigh effort) inside Codex CLI; unmodified original output."
|
||||
},
|
||||
"contributor": "YinL-k"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.5 Pro",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "ChatGPT Web",
|
||||
"effort": "Extended Pro",
|
||||
"artifactDir": "gpt-5-5-pro/chatgpt-web-extended-pro",
|
||||
"order": 29.8,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "以 GPT-5.5 Pro(开启 Extended Pro)生成;本页为修复版(仅改 1 行)。问题:14 张贴图排成 8×2 图集、第二行只画了 6 格,UV 按左上原点计算却未设 flipY=false,两行贴图上下互换采样。修复前:草地顶面显示成黑曜石(地面全黑),树叶、沙子采到未绘制的空格(大片纯黑),放置方块的贴图互相串行、颜色诡异似反色。原因:CanvasTexture 默认 flipY=true,与自上而下的图集布局不匹配。加上 flipY=false 后地形与方块颜色全部正常。",
|
||||
"en": "Generated with GPT-5.5 Pro (Extended Pro enabled); fixed build (1 line changed). Bug: 14 tiles in an 8x2 atlas with only 6 slots drawn in row two, UVs computed from a top-left origin without flipY=false, so the two rows swapped on sampling. Before the fix: grass tops rendered as obsidian (black ground), leaves and sand hit undrawn slots (large black patches), and placed blocks wore each other's textures with inverted-looking colors. Cause: CanvasTexture defaults to flipY=true, mismatching the top-down atlas layout. With flipY=false everything renders correctly."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 ChatGPT 网页版以 GPT-5.5 Pro(开启 Extended Pro)一次生成。",
|
||||
"en": "Generated in one shot in the ChatGPT web app on GPT-5.5 Pro (Extended Pro enabled)."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 ChatGPT 网页版以 GPT-5.5 Pro(开启 Extended Pro)一次生成;本页为修复版(改 2 行)。问题一:发条与游丝用 createSpiralTube 构建(760 行),管线段数写成 Math.max(96, turns * 38),turns 为 5.8 / 6.4 时算出 220.4 / 243.2 的小数段数。修复前:TubeGeometry 末尾会以该段数作下标取 Frenet 帧 frames.normals[220.4],得到 undefined 后读取 .x 抛 TypeError,初始化中断,页面卡在「Assembling jeweled movement...」加载遮罩永不消失。原因:模型把管线段数当连续值传入,未取整。改为 Math.round(turns * 38) 后段数为整数,场景正常加载、遮罩按时隐藏。问题二:场景中心有一团异常刺眼的辉光,几乎把整块表盘糊成纯白,什么都看不清,放大观察时尤其严重。原因:UnrealBloomPass(340 行)阈值过低(threshold 0.18)且强度偏高(strength 0.72),叠加 ACES 色调映射与那盏强度 96 的主聚光灯打在抛光夹板上的近白高光,几乎所有金属高光与夜光都被纳入泛光并扩散成一大团过曝白光。实测仅把阈值/强度收敛仍不够——近距离时抛光中心板的高光依旧被放大成亮雾。为保护测试者的眼睛、避免对光敏性癫痫人群造成风险,直接关闭 bloom(strength 设为 0):机芯、齿轮、指针、夹板恢复清晰锐利,夜光时标仍靠材质自发光正常显示。",
|
||||
"en": "Generated in one shot in the ChatGPT web app on GPT-5.5 Pro (Extended Pro enabled); fixed build (2 lines changed). Bug 1: the mainspring and hairspring are built via createSpiralTube (line 760) with the tube segment count written as Math.max(96, turns * 38), so turns of 5.8 / 6.4 yield fractional counts 220.4 / 243.2. Before the fix: TubeGeometry's trailing generateSegment indexes the Frenet-frame arrays with that count (frames.normals[220.4]), gets undefined and reads .x, throwing a TypeError that aborts init and leaves the page stuck on the 'Assembling jeweled movement...' loading overlay forever. Cause: the model passed the tube segment count as a continuous value without rounding. Changing it to Math.round(turns * 38) makes the count an integer, so the scene loads and the overlay hides on schedule. Bug 2: an abnormally blinding glow fills the center of the scene, washing the whole dial to pure white so nothing is legible, and it is worst when zoomed in. Cause: UnrealBloomPass (line 340) used too low a threshold (0.18) and a high strength (0.72), which together with ACES tone mapping and the near-white specular the intensity-96 key spotlight casts on the polished plate pulled nearly every metallic highlight and lume surface into the bloom and spread it into one big over-exposed white blob. Merely lowering the threshold/strength was not enough — up close the polished center plate's highlight still blew out into a glowing haze. To protect testers' eyes and avoid any risk to people with photosensitive epilepsy, the bloom was disabled outright (strength set to 0): the movement, gears, hands and bridges are crisp again, and the lume markers still read via their emissive material."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.5",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Codex CLI",
|
||||
"effort": "xhigh",
|
||||
"artifactDir": "gpt-5-5/codex-cli-xhigh",
|
||||
"order": 30,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "全程在 Codex CLI 中生成(未使用任何 skill);本页为修复版(仅改 1 行)。问题:移动向量的 Z 分量整体差一个负号(第 979 行,正确应为 -strafe*sin - forward*cos)。修复前:运动被沿 X 轴镜像——面朝 ±X 方向时按 A/D 左右颠倒,面朝 ±Z 时 W/S 前后颠倒,体感「左右移动方向不对」。原因:Three.js 相机在 yaw=0 时朝 -Z,推导前进/侧移向量时把这个约定弄反了。给 Z 分量取负后,任意朝向下八方向移动全部正确。",
|
||||
"en": "Generated entirely in Codex CLI (no skills); fixed build (1 line changed). Bug: the movement vector's Z component had its sign flipped (line 979; correct form is -strafe*sin - forward*cos). Before the fix movement was mirrored across the X axis — A/D swapped when facing +-X, W/S swapped when facing +-Z, i.e. \"strafing goes the wrong way\". Cause: a Three.js camera faces -Z at yaw 0, and the forward/right derivation inverted that convention. Negating Z makes all eight directions correct at any heading."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "由社区 PR #3(Codex 工作流)提交,提交者未注明运行环境与思考档位。",
|
||||
"en": "Contributed via community PR #3 (a Codex workflow); the submitter did not specify the runtime or effort level."
|
||||
},
|
||||
"contributor": "XA2333"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Codex CLI 中以 GPT-5.5(xhigh 思考档位,未使用任何 skill)一次生成;本页为未经修改的原始产出。提示词开头明确禁止阅读或参考本目录及仓库中的任何其它文件,要求完全凭模型自身知识独立完成。",
|
||||
"en": "Generated in one shot by GPT-5.5 (xhigh effort, no skills) in Codex CLI; unmodified original output. The prompt explicitly forbade reading or referencing any other file in the directory or repository, requiring the model to work solely from its own knowledge."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.6-Sol",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Codex CLI",
|
||||
"effort": "max",
|
||||
"order": 29.1,
|
||||
"runs": {
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "GPT-5.6-Sol(max 思考档位)在 Codex CLI 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则,策略文件不公开、仅注册运行,由维护者亲自执行。自命名「界刃」。",
|
||||
"en": "One-shot Turf War agent by GPT-5.6-Sol (max effort) in Codex CLI from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished; this entry registers the maintainer-executed run. Self-named \"界刃\"."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "界刃"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.6-Sol",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Codex CLI",
|
||||
"effort": "xhigh",
|
||||
"order": 29.2,
|
||||
"runs": {
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "GPT-5.6-Sol(xhigh 思考档位)在 Codex CLI 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则,策略文件不公开、仅注册运行,由维护者亲自执行。自命名「蚀界」。",
|
||||
"en": "One-shot Turf War agent by GPT-5.6-Sol (xhigh effort) in Codex CLI from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished; this entry registers the maintainer-executed run. Self-named \"蚀界\"."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "蚀界"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.6-Sol",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Codex CLI",
|
||||
"effort": "ultra",
|
||||
"artifactDir": "gpt-5-6-sol/codex-cli-ultra",
|
||||
"order": 29,
|
||||
"runs": {
|
||||
"mythos-craft": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Codex 应用内置的 Codex CLI 0.144.0-alpha.4 中以 GPT-5.6-Sol(ultra 思考档位),使用仓库标准中文提示词生成。此 case 使用独立、全新的 CODEX_HOME,并从空工作目录开始,未加载 harness 自带以外的任何 skill、插件、MCP、自定义规则或系统提示。原始 Ultra 回合在已完整写出可玩单 HTML 后,因宿主收到状态查询而在最终加固阶段中断;本页是中断前落盘的完整 fallback.html,仅原样重命名,未修改内容,也未重试生成。",
|
||||
"en": "Generated from the repository's canonical Chinese prompt by GPT-5.6-Sol (ultra effort) in Codex CLI 0.144.0-alpha.4 bundled with the Codex app. This case used a separate clean CODEX_HOME and started in an empty working directory, with no skills, plugins, MCP servers, custom rules, or system prompts beyond those bundled with the harness. The original Ultra turn was host-interrupted during final hardening after it had written a complete playable single-file HTML; this page is that saved fallback.html, renamed verbatim with no content changes and no retry."
|
||||
},
|
||||
"contributor": "ZidongChen25"
|
||||
},
|
||||
{
|
||||
"file": "mythos-craft-block-era.html",
|
||||
"note": {
|
||||
"zh": "同一 case 的另一版一次生成产物,与主产物并列保留。GPT-5.6-Sol(ultra 思考档位)在 Codex CLI 中用仓库标准中文提示词生成,未经人工修改。此版为『方块纪元』主题的沙盒建造。",
|
||||
"en": "An alternate one-shot artifact for this case, kept alongside the primary run. Generated by GPT-5.6-Sol (ultra effort) in Codex CLI from the repository's canonical Chinese prompt with no human edits. A 'Block Era' sandbox-building take."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
],
|
||||
"pelican-cycling": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Codex 应用内置的 Codex CLI 0.144.0-alpha.4 中以 GPT-5.6-Sol(ultra 思考档位),使用仓库标准中文提示词一次生成。此 case 使用独立、全新的 CODEX_HOME,并从空工作目录开始,未加载 harness 自带以外的任何 skill、插件、MCP、自定义规则或系统提示;本页为未经人工修改的原始 agent 产物。",
|
||||
"en": "Generated in one shot from the repository's canonical Chinese prompt by GPT-5.6-Sol (ultra effort) in Codex CLI 0.144.0-alpha.4 bundled with the Codex app. This case used a separate clean CODEX_HOME and started in an empty working directory, with no skills, plugins, MCP servers, custom rules, or system prompts beyond those bundled with the harness; this is the original agent artifact with no human edits."
|
||||
},
|
||||
"contributor": "ZidongChen25"
|
||||
},
|
||||
{
|
||||
"file": "pelican-coastal-ride.svg",
|
||||
"note": {
|
||||
"zh": "同一 case 的另一版一次生成 SVG,与主产物并列保留。GPT-5.6-Sol(ultra 思考档位)在 Codex CLI 中用仓库标准中文提示词生成,未经人工修改。",
|
||||
"en": "An alternate one-shot SVG for this case, kept alongside the primary run. Generated by GPT-5.6-Sol (ultra effort) in Codex CLI from the repository's canonical Chinese prompt with no human edits."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "pelican-cyclist.svg",
|
||||
"note": {
|
||||
"zh": "同一 case 的又一版一次生成 SVG,与主产物并列保留。GPT-5.6-Sol(ultra 思考档位)在 Codex CLI 中用仓库标准中文提示词生成,未经人工修改。",
|
||||
"en": "A further alternate one-shot SVG for this case, kept alongside the primary run. Generated by GPT-5.6-Sol (ultra effort) in Codex CLI from the repository's canonical Chinese prompt with no human edits."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
],
|
||||
"skeleton-watch": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Codex 应用内置的 Codex CLI 0.144.0-alpha.4 中以 GPT-5.6-Sol(ultra 思考档位),使用仓库标准中文提示词一次生成。此 case 使用独立、全新的 CODEX_HOME,并从空工作目录开始,未加载 harness 自带以外的任何 skill、插件、MCP、自定义规则或系统提示。原始 Ultra 回合已生成完整 HTML 写入调用,但宿主状态查询在该调用执行前将回合中断;本页通过原样重放 rollout 中完整记录的 apply_patch 参数恢复,未续写模型,也未修改内容。",
|
||||
"en": "Generated in one shot from the repository's canonical Chinese prompt by GPT-5.6-Sol (ultra effort) in Codex CLI 0.144.0-alpha.4 bundled with the Codex app. This case used a separate clean CODEX_HOME and started in an empty working directory, with no skills, plugins, MCP servers, custom rules, or system prompts beyond those bundled with the harness. The original Ultra turn had produced the complete HTML write call when a host status query interrupted the turn before that call executed; this page was recovered by replaying the exact apply_patch payload recorded in the rollout, with no model continuation or content changes."
|
||||
},
|
||||
"contributor": "ZidongChen25"
|
||||
},
|
||||
{
|
||||
"file": "skeleton-watch-calibre-01.html",
|
||||
"note": {
|
||||
"zh": "同一 case 的另一次独立、完整生成(『Calibre 01』),与主产物(受宿主中断后恢复的 AURELIUS S-01)并列保留。GPT-5.6-Sol(ultra 思考档位)在 Codex CLI 中用仓库标准中文提示词生成,未经人工修改。",
|
||||
"en": "A separate, complete generation for this case ('Calibre 01'), kept alongside the primary run (the interruption-recovered AURELIUS S-01). Generated by GPT-5.6-Sol (ultra effort) in Codex CLI from the repository's canonical Chinese prompt with no human edits."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.6-Terra",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Codex CLI",
|
||||
"effort": "xhigh",
|
||||
"order": 29.6,
|
||||
"runs": {
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "GPT-5.6-Terra(xhigh 思考档位)在 Codex CLI 中用仓库标准中文提示词(仅按案例规则填写文末【本次运行】占位符)一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「拓荒狐」。诊断:工程翻车:自写的 addTop 插入排序有 off-by-one,数组留下 undefined 空洞,act() 几乎每个 tick 都抛 TypeError(top[at-1].raw)。按规则抛异常即该 tick 直行,于是它全程走直线、策略一步未跑成。这是它真实的一次成型水平。",
|
||||
"en": "One-shot Turf War agent by GPT-5.6-Terra (xhigh effort) in Codex CLI from the repository's canonical Chinese prompt (only the [This run] placeholders filled per case rules), with no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"拓荒狐\". Diagnosis: Engineering failure: its own addTop insertion-sort has an off-by-one that leaves an undefined hole, so act() throws a TypeError (top[at-1].raw) almost every tick. Per the rules a thrown act() means a straight move, so it walks in a straight line all match — its real strategy never runs once."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "拓荒狐"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "GPT-5.6-Terra",
|
||||
"vendor": "OpenAI",
|
||||
"harness": "Codex CLI",
|
||||
"effort": "ultra",
|
||||
"artifactDir": "gpt-5-6-terra/codex-cli-ultra",
|
||||
"order": 29.5,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "GPT-5.6-Terra(ultra 思考档位)在 Codex CLI 中用仓库标准中文提示词一次生成,未经人工修改。此版为『阿斯特拉:方块神话』主题的沙盒建造。",
|
||||
"en": "One-shot by GPT-5.6-Terra (ultra effort) in Codex CLI from the repository's canonical Chinese prompt, with no human edits. An 'Astra: Block Myth' sandbox-building take."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "GPT-5.6-Terra(ultra 思考档位)在 Codex CLI 中用仓库标准中文提示词一次生成,未经人工修改。海边骑行鹈鹕 SVG。",
|
||||
"en": "One-shot by GPT-5.6-Terra (ultra effort) in Codex CLI from the repository's canonical Chinese prompt, with no human edits. A seaside pelican-on-bicycle SVG."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "GPT-5.6-Terra(ultra 思考档位)在 Codex CLI 中用仓库标准中文提示词一次生成,未经人工修改。镂空机械腕表交互演示。",
|
||||
"en": "One-shot by GPT-5.6-Terra (ultra effort) in Codex CLI from the repository's canonical Chinese prompt, with no human edits. An interactive skeleton mechanical-watch demo."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Grok 4.3",
|
||||
"vendor": "xAI",
|
||||
"harness": "Cursor",
|
||||
"effort": "Default",
|
||||
"artifactDir": "grok-4-3/cursor-default",
|
||||
"order": 61,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 Grok 4.3 默认设置一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Grok 4.3 inside Cursor CLI with default settings; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Cursor CLI 中以 Grok 4.3 默认设置一次生成;本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Grok 4.3 inside Cursor CLI with default settings; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "Grok 4.5",
|
||||
"vendor": "xAI",
|
||||
"harness": "Grok Build TUI",
|
||||
"effort": "High",
|
||||
"artifactDir": "grok-4-5/grok-build-tui-high",
|
||||
"order": 59,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在本地通过 Grok Build(TUI)以 Grok 4.5(High,最高可选档位)一次生成,未修改。含 Perlin 噪声地形、WASD 第一人称、多种方块破坏/放置、水晶塔/符文石阵/龙骨遗迹/天空遗迹,按 G 传送地标,附小地图与快捷栏。",
|
||||
"en": "One-shot by Grok 4.5 (High, highest available effort) in the Grok Build TUI, unmodified. Perlin-noise terrain, WASD first-person, multi-block break/place, Crystal Spire / Rune Circle / Dragon Relic / Sky Ruins, G-key landmark teleport, minimap and hotbar."
|
||||
},
|
||||
"contributor": "Mintcolour"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在本地通过 Grok Build(TUI)以 Grok 4.5(High,最高可选档位)一次生成,未修改。海边骑行鹈鹕 SVG:渐变天空、太阳云朵、海浪沙滩、棕榈树、海鸥、自行车细节与鹈鹕喙囊羽翼踩踏姿态。",
|
||||
"en": "One-shot by Grok 4.5 (High, highest available effort) in the Grok Build TUI, unmodified. Coastal pelican-on-bicycle SVG: gradient sky, sun and clouds, waves and beach, palms, gulls, detailed bicycle, and pelican beak/pouch/wing pedaling pose."
|
||||
},
|
||||
"contributor": "Mintcolour"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在本地通过 Grok Build(TUI)以 Grok 4.5(High,最高可选档位)一次生成,未修改。Three.js 镂空机械表:齿轮系、摆轮游丝、擒纵、红宝石轴承、发光指针刻度、爆炸/组装视图、零件悬停标签与自动旋转。",
|
||||
"en": "One-shot by Grok 4.5 (High, highest available effort) in the Grok Build TUI, unmodified. Three.js skeleton watch: gear train, balance/hairspring, escapement, ruby bearings, luminous hands and markers, exploded/assembled views, part hover labels and auto-rotate."
|
||||
},
|
||||
"contributor": "Mintcolour"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Grok Build",
|
||||
"vendor": "xAI",
|
||||
"harness": "Grok Build TUI",
|
||||
"effort": "Max",
|
||||
"artifactDir": "grok-build/grok-build-tui-max",
|
||||
"order": 60,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在本地通过 Grok Build(TUI)开发实现;本页为修复版(共改 3 行)。问题一:第 82 行 const WORLD_SEED = 0xAETHER,T/H/R 不是十六进制字符,整段脚本解析即抛 SyntaxError——修复前画面纯黑,只剩 HTML 文字。问题二:贴图集画在 canvas 顶部两行、UV 按左上原点计算,却未设 flipY=false,所有面采样到未绘制的黑色区域——修复前进入世界后近处地形全黑。问题三:forward 向量已指向相机朝向,按键却写成 W 减、S 加——修复前按 W 后退、按 S 前进(A/D 正常)。三处均为一行级修正,其余保持 Grok 原样。",
|
||||
"en": "Built locally with the Grok Build TUI; fixed build (3 lines changed). Bug 1: line 82 const WORLD_SEED = 0xAETHER — T/H/R are not hex digits, so the whole script threw a SyntaxError at parse time; before the fix the screen was pure black with only HTML text. Bug 2: the atlas is drawn in the top canvas rows with top-left-origin UVs but no flipY=false, so every face sampled the undrawn black region; before the fix nearby terrain rendered solid black. Bug 3: the forward vector already points where the camera looks, but W subtracted and S added — W walked backward (A/D were fine). One line each; everything else is Grok as-is."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在本地通过 Grok Build(TUI)一次生成。",
|
||||
"en": "Generated in one shot locally via the Grok Build TUI."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Inkling",
|
||||
"vendor": "Thinking Machines Lab",
|
||||
"harness": "Tinker Web",
|
||||
"effort": "xHigh",
|
||||
"artifactDir": "inkling/tinker-web-xhigh",
|
||||
"order": 135,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Tinker 网页版(Inkling Playground)以 Inkling(xHigh 档位)一次生成,未进行任何修改。",
|
||||
"en": "Generated in one shot with Inkling (xHigh) in the Tinker web playground, unmodified."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Tinker 网页版(Inkling Playground)以 Inkling(xHigh 档位)一次生成,未进行任何修改。",
|
||||
"en": "Generated in one shot with Inkling (xHigh) in the Tinker web playground, unmodified."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Kimi K2.6",
|
||||
"vendor": "Moonshot AI",
|
||||
"harness": "Kimi Code",
|
||||
"effort": "Thinking",
|
||||
"artifactDir": "kimi-k2-6/kimi-code-thinking",
|
||||
"order": 100,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在本地以 Kimi Code(harness)运行 Kimi K2.6 一次生成,本页为未经修改的原始产出。",
|
||||
"en": "Generated in one shot by Kimi K2.6 running locally in Kimi Code; unmodified original output."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在本地以 Kimi Code(harness)运行 Kimi K2.6 一次生成。",
|
||||
"en": "Generated in one shot by Kimi K2.6 running locally in Kimi Code."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "Kimi K2.7-Code",
|
||||
"vendor": "Moonshot AI",
|
||||
"harness": "Kimi Code",
|
||||
"effort": "Thinking",
|
||||
"artifactDir": "kimi-k2-7-code/kimi-code-thinking",
|
||||
"order": 98,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Kimi Code 中使用 Kimi K2.7-Code 模型(Thinking 档位)生成。修复了 Chunk 生成与获取递归调用导致的调用栈溢出(Maximum call stack size exceeded)报错,并调整了 Canvas 层级定位问题。",
|
||||
"en": "Generated by Kimi K2.7-Code running in Kimi Code with Thinking mode. Fixed a call stack size exceeded recursion crash in Chunk generation, and resolved canvas absolute positioning issues."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Kimi Code 中使用 Kimi K2.7-Code 模型(Thinking 档位)一次生成。",
|
||||
"en": "Generated in one shot by Kimi K2.7-Code running in Kimi Code with Thinking mode."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Kimi Code 中使用 Kimi K2.7-Code 模型(Thinking 档位)一次生成,未进行任何修改。",
|
||||
"en": "Generated in one shot by Kimi K2.7-Code running in Kimi Code with Thinking mode, unmodified."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"label": "Kimi K3",
|
||||
"vendor": "Moonshot AI",
|
||||
"harness": "Kimi Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "kimi-k3/kimi-code-max",
|
||||
"order": 97.85,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Kimi Code 中以 Kimi K3(Max 档位)一次生成,未经修改。与「Kimi 网页版」条目同模型、不同 Harness,可并排对比。",
|
||||
"en": "Generated in one shot by Kimi K3 (Max) inside Kimi Code, unmodified. Same model as the Kimi Web entry under a different harness — compare the two side by side."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Kimi Code 中以 Kimi K3(Max 档位)一次生成。",
|
||||
"en": "Generated in one shot by Kimi K3 (Max) inside Kimi Code."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"turf-war": {
|
||||
"note": {
|
||||
"zh": "Kimi K3(Kimi Code · Max)用仓库标准中文提示词一次生成的圈地智能体,未经人工修改。按案例规则策略文件不公开、仅注册运行,由维护者亲自执行,任何 bug 均不修复。自命名「蚕食」。诊断:合法且 fuel 安全——生存不变量完整(绝不踩己尾、每步保留回家路)、断尾反杀与威胁 BFS 撤退逻辑齐全。但实战偏弱:对 baseline-expander 两个种子均告负(318:1045、339:939),扩张过于保守、圈地量小(约 80–92 手却只得 300 出头)。写得干净却太求稳,不敢铺开,被基线三比一碾过。",
|
||||
"en": "One-shot Turf War agent by Kimi K3 (Kimi Code, Max) from the repository's canonical Chinese prompt, no human edits. Per case rules the policy file is unpublished and no bug is ever fixed; this registers the maintainer-executed run. Self-named \"蚕食\" (Nibble). Diagnosis: legal and fuel-safe — complete survival invariants (never steps on its own tail, always keeps a path home), full tail-cut punish and threat-BFS retreat. But competitively weak: loses to baseline-expander on both seeds (318:1045, 339:939), expanding too conservatively for a small capture volume (~80–92 moves for barely 300 points). Clean code that plays too safe to spread out, run over ~3:1 by the baseline."
|
||||
},
|
||||
"contributor": "Nagi-ovo",
|
||||
"agentName": "蚕食"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "Kimi K3",
|
||||
"vendor": "Moonshot AI",
|
||||
"harness": "Kimi Web",
|
||||
"effort": "Max",
|
||||
"artifactDir": "kimi-k3/kimi-web-max",
|
||||
"order": 97.9,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Kimi 网页版使用 Kimi K3(Max 档位)一次生成,未进行任何修改。",
|
||||
"en": "Generated in one shot in the Kimi web app on Kimi K3 (Max), unmodified."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
{
|
||||
"label": "MiMo V2.5",
|
||||
"vendor": "Xiaomi",
|
||||
"harness": "MiMo Code",
|
||||
"effort": "High",
|
||||
"artifactDir": "mimo-v2-5/micode-high",
|
||||
"order": 97,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 MiMo Code (v0.1.4) 中以 MiMo V2.5(High 思考强度)一次生成;原始产出修复了多个严重 bug(Perlin noise permutation 数组初始化错误导致 NaN 索引、noise 函数作用域问题、noiseFill 变量命名冲突、函数名误用点号、this 缺少点号、IIFE 未闭合、Three.js RepeatSetting 常量错误、WASD 移动按键错误、沙子/草地判断逻辑反转、物理引擎碰撞检测逻辑错误导致着地弹跳),并重写了整个噪声模块和物理引擎,将 Three.js 内联以支持本地 file:// 协议运行,生成逻辑未改。基于 Three.js 构建了完整的 3D 体素世界,包含柏林噪声生成的地形、WASD + 鼠标第一人称操控、8 种方块类型(泥土、草、石头、沙、木头、砖、水晶、黄金)、左键破坏右键放置、飞行模式、以及五处神话幻想建筑(水晶塔、石阵、龙骨遗迹、天空岛、远古图书馆)。",
|
||||
"en": "Generated in one shot by MiMo V2.5 (High effort) inside MiMo Code (v0.1.4); original output had multiple critical bugs fixed (Perlin noise permutation array initialization error causing NaN indexing, noise function scope issue, noiseFill variable naming collision, dot after function keyword, missing dot after this, unclosed IIFE, Three.js RepeatSetting constant error, WASD movement key error, sand/grass logic inversion, physics engine collision detection error causing ground bouncing); noise module and physics engine rewritten entirely, Three.js inlined for local file:// protocol compatibility; generation logic untouched. Built a complete 3D voxel world with Three.js, featuring Perlin noise terrain, WASD + mouse first-person controls, 8 block types (dirt, grass, stone, sand, wood, brick, crystal, gold), left-click break / right-click place, fly mode, and five mythic-fantasy structures (Crystal Tower, Stone Circle, Dragon Ruins, Sky Island, Ancient Library)."
|
||||
},
|
||||
"contributor": "BaiLin-William"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 MiMo Code (v0.1.4) 中以 MiMo V2.5(High 思考强度)一次生成;未经修改的原始产出。包含渐变天空、太阳光晕、多层云朵、海浪纹理、浪花泡沫线、湿润沙滩、棕榈树(含椰子)、海鸥、鹈鹕(解剖细节:喉囊、喙钩、羽冠、翅膀羽毛纹理、蹼足)、红色自行车(辐条、链条、踏板、车座、挡泥板)、海星贝壳脚印等装饰元素。",
|
||||
"en": "Generated in one shot by MiMo V2.5 (High effort) inside MiMo Code (v0.1.4); unmodified original output. Includes gradient sky, sun glow, layered clouds, ocean wave textures, foam line, wet sand, palm trees with coconuts, seagulls, a detailed pelican (anatomical: gular pouch, beak hook, crown tuft, wing feather texture, webbed feet), red bicycle (spokes, chain, pedals, seat, fender), and beach decorations (starfish, shells, footprints)."
|
||||
},
|
||||
"contributor": "BaiLin-William"
|
||||
},
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 MiMo Code (v0.1.4) 中以 MiMo V2.5(High 思考强度)一次生成;将 Three.js 内联以支持本地 file:// 协议运行。原始产出在机芯型号中写出 MiMo,会在 Arena 盲评中泄露身份;本页仅将该型号改为中性 SK-01,其余未修改。基于 Three.js 构建了完整的 3D 镂空机械腕表,包含发条盒(含可见游丝发条)、齿轮系(5 组齿轮)、擒纵机构(擒纵轮+擒纵叉+红宝石瓦)、摆轮(带宝玑游丝、调速螺丝、冲击销)、夹板(日内瓦纹主夹板、摆轮夹板、发条盒夹板)、35 颗红宝石轴承、时分秒针(夜光涂层)、12 个小时刻度(3/6/9/12 为金色条形+夜光点)、60 格分钟刻度、蓝宝石水晶表镜与底盖、玫瑰金表壳表圈、表冠(带防滑纹路)、表耳。交互功能:拖拽旋转、滚轮缩放、E 键切换爆炸视图、I 键显示机芯信息面板、空格暂停动画、悬停零件标签高亮。灯光:ACES 色调映射、三点布光(主光/填充光/轮廓光)+ 顶部补光。",
|
||||
"en": "Generated in one shot by MiMo V2.5 (High effort) inside MiMo Code (v0.1.4); Three.js inlined for local file:// protocol compatibility. The original output included MiMo in the calibre model, which would reveal identity in blind Arena mode; this page only changes that visible model to neutral SK-01, otherwise unmodified. Built a complete 3D skeleton mechanical watch with Three.js, featuring mainspring barrel (visible coiled spring), gear train (5 wheels), escapement (escape wheel + pallet fork with ruby pallets), balance wheel (Breguet hairspring, timing screws, impulse pin), bridges (Côtes de Genève main bridge, balance cock, barrel bridge), 35 ruby jewel bearings, luminous hour/minute/second hands, 12 hour markers (gold bar at 3/6/9/12 with lume dot), 60-minute track, sapphire crystal front and caseback, rose gold case and bezel, knurled crown, and lugs. Interactions: drag rotate, scroll zoom, E for exploded view, I for movement info panel, Space to pause, hover labels. Lighting: ACES tone mapping, three-point studio lighting."
|
||||
},
|
||||
"contributor": "BaiLin-William"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"label": "MiMo v2.5 Pro",
|
||||
"vendor": "Xiaomi",
|
||||
"harness": "MiMo Code",
|
||||
"effort": "Max",
|
||||
"order": 97.2,
|
||||
"runs": {
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 MiMo Code 中使用 MiMo v2.5 Pro 模型一次生成,未进行任何修改。",
|
||||
"en": "Generated in one shot by MiMo v2.5 Pro in MiMo Code, unmodified."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"label": "MiMo v2.5 Pro UltraSpeed",
|
||||
"vendor": "Xiaomi",
|
||||
"harness": "Web",
|
||||
"effort": "Default",
|
||||
"order": 97.5,
|
||||
"runs": {
|
||||
"mythos-craft": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Web 环境中使用 MiMo v2.5 Pro UltraSpeed 一次生成,未做任何修改。",
|
||||
"en": "Generated in one shot by MiMo v2.5 Pro UltraSpeed in Web environment, with no modifications."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
},
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Web 环境中使用 MiMo v2.5 Pro UltraSpeed 一次生成,未做任何修改(变体 2)。",
|
||||
"en": "Generated in one shot by MiMo v2.5 Pro UltraSpeed in Web environment, with no modifications (variant 2)."
|
||||
},
|
||||
"file": "mythos-craft-2.html",
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
],
|
||||
"pelican-cycling": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Web 环境中使用 MiMo v2.5 Pro UltraSpeed 一次生成,未做任何修改。",
|
||||
"en": "Generated in one shot by MiMo v2.5 Pro UltraSpeed in Web environment, with no modifications."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
},
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 Web 环境中使用 MiMo v2.5 Pro UltraSpeed 一次生成,未做任何修改(变体 2)。",
|
||||
"en": "Generated in one shot by MiMo v2.5 Pro UltraSpeed in Web environment, with no modifications (variant 2)."
|
||||
},
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
],
|
||||
"skeleton-watch": {
|
||||
"note": {
|
||||
"zh": "在 Web 环境中使用 MiMo v2.5 Pro UltraSpeed 一次生成,未做任何修改。",
|
||||
"en": "Generated in one shot by MiMo v2.5 Pro UltraSpeed in Web environment, with no modifications."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"label": "MiniMax M2.7",
|
||||
"vendor": "MiniMax",
|
||||
"harness": "MiniMax Code Web",
|
||||
"effort": "Thinking",
|
||||
"artifactDir": "minimax-m2-7/minimax-code-web-thinking",
|
||||
"order": 111,
|
||||
"runs": {
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 MiniMax Code 网页版以 MiniMax M2.7(开启思考模式 Thinking)一次生成。",
|
||||
"en": "Generated in one shot by MiniMax M2.7 (Thinking mode on) in the MiniMax Code web app."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"label": "MiniMax M3",
|
||||
"vendor": "MiniMax",
|
||||
"harness": "MiniMax Code Web",
|
||||
"effort": "Thinking",
|
||||
"artifactDir": "minimax-m3/minimax-code-web-thinking",
|
||||
"order": 110,
|
||||
"runs": {
|
||||
"pelican-cycling": [
|
||||
{
|
||||
"note": {
|
||||
"zh": "在 MiniMax Code 网页版以 MiniMax M3(开启思考模式 Thinking)一次生成。",
|
||||
"en": "Generated in one shot by MiniMax M3 (Thinking mode on) in the MiniMax Code web app."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
{
|
||||
"file": "pelican-cycling-2.svg",
|
||||
"note": {
|
||||
"zh": "在 MiniMax Code 网页版以 MiniMax M3(开启思考模式 Thinking)一次生成的第二个 SVG 版本。",
|
||||
"en": "Second SVG variant generated in one shot by MiniMax M3 (Thinking mode on) in the MiniMax Code web app."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Mistral Medium 3.5",
|
||||
"vendor": "Mistral AI",
|
||||
"harness": "Vibe",
|
||||
"effort": "Thinking",
|
||||
"artifactDir": "mistral-medium-3-5/vibe-thinking",
|
||||
"order": 80,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Vibe 网页版以 Mistral Medium 3.5(开启 Thinking)一次生成;本页为修复版(仅改 1 行)。问题:初始化时一次性同步生成 RENDER_DISTANCE=4 的 9×9=81 个区块,而每个区块要对 16×16×128 个体素逐一在 mythStructures 数组里做线性查找(再加 6 个相邻面),约 O(体素×建筑)≈ 数十亿次/区块。修复前:加载后主线程被卡死,页面一直停在加载界面、始终无法打开。原因:首屏同步生成量过大且单区块为暴力线性查找,首帧渲染前即阻塞主线程。将 RENDER_DISTANCE 由 4 改为 1(首屏改为同步生成 3×3=9 个区块)后即可正常打开并以 60fps 运行。地形、画面与玩法均为模型原始产出,未作他改。",
|
||||
"en": "Generated in one shot in the Vibe web app on Mistral Medium 3.5 (Thinking enabled); fixed build (1 line changed). Bug: on init the world synchronously builds the full RENDER_DISTANCE=4 ring — a 9x9 = 81-chunk grid — and each chunk runs a linear scan over the mythStructures array for every one of its 16x16x128 voxels (plus six neighbor faces), roughly O(voxels x structures) ≈ billions of ops per chunk. Before the fix: the main thread froze on load and the page stayed on the loading screen, never opening. Cause: the first-screen synchronous generation is far too large and each chunk uses a brute-force linear lookup, blocking the main thread before the first frame. Setting RENDER_DISTANCE from 4 to 1 (a 3x3 = 9-chunk first-screen load) lets it open and run at 60fps. Terrain, visuals and gameplay are the model's original output, otherwise unmodified."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Vibe 网页版以 Mistral Medium 3.5(开启 Thinking)一次生成,未作修改。",
|
||||
"en": "Generated in one shot in the Vibe web app on Mistral Medium 3.5 (Thinking enabled), unmodified."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Nex-N2-Pro",
|
||||
"vendor": "Nex-AGI",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "nex-n2-pro/claude-code-max",
|
||||
"order": 65,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "通过 Claude Code 生成(thinking effort: max);一次生成,未经过人工后续修改。",
|
||||
"en": "Generated via Claude Code (thinking effort: max); one-shot, without manual modifications."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "通过 Claude Code 生成(thinking effort: max);一次生成,未经过人工后续修改。",
|
||||
"en": "Generated via Claude Code (thinking effort: max); one-shot, without manual modifications."
|
||||
},
|
||||
"contributor": "OHCJ099"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Qwen3.7-Max",
|
||||
"vendor": "Alibaba",
|
||||
"harness": "Qoder",
|
||||
"effort": "Default",
|
||||
"artifactDir": "qwen3-7-max/qoder-default",
|
||||
"order": 130,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Qoder 中由 Qwen3.7-Max 一次生成,本页为未经修改的原始产出。基于 Three.js 的体素世界:区块地形、第一人称 WASD 控制、方块破坏/放置,并自带奇幻建筑(法师塔、远古神庙、巨树、龙巢、符文阵)。",
|
||||
"en": "Generated in one shot by Qwen3.7-Max inside Qoder; unmodified original output. A Three.js voxel world: chunked terrain, first-person WASD controls, block break/place, with fantasy structures (wizard tower, ancient temple, giant tree, dragon nest, runic circle)."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Qoder 中由 Qwen3.7-Max 一次生成,本页为未经修改的原始 SVG。海边场景:太阳、云朵、海鸥、远处群山与帆船、椰子树,一只鹈鹕骑着红色自行车(含速度线),左上角附一张鹈鹕侧面图鉴小卡。",
|
||||
"en": "Generated in one shot by Qwen3.7-Max inside Qoder; unmodified original SVG. A seaside scene — sun, clouds, gulls, distant mountains and a sailboat, a palm tree, and a pelican riding a red bicycle (with motion lines), plus a pelican side-profile study card in the top-left corner."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "Step 3.7 Flash",
|
||||
"vendor": "StepFun",
|
||||
"harness": "Claude Code",
|
||||
"effort": "High",
|
||||
"artifactDir": "step-3-7-flash/claude-code-high",
|
||||
"order": 131,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Step 3.7 Flash(High 思考强度)一次生成。生成时 Write 工具调用失误,模型产出的内容由人工手动写入该 HTML 文件,代码本身未做改动。基于 Three.js 与柏林噪声地形的《我的世界》式体素世界,支持 WASD + 鼠标第一人称操控。",
|
||||
"en": "Generated in one shot by Step 3.7 Flash in Claude Code (High thinking effort). The Write tool call failed during generation, so the model's output was saved into this HTML file by hand; the code itself was not modified. A Minecraft-style voxel world built on Three.js with Perlin-noise terrain and WASD + mouse first-person controls."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 Step 3.7 Flash(High 思考强度)一次生成,未做人工修复。海边骑行的鹈鹕 SVG:蓝天白云、海面、沙滩与海鸥,红色自行车(含辐条与链条),鹈鹕有长喙与喉囊。",
|
||||
"en": "Generated in one shot via Claude Code with Step 3.7 Flash (High thinking effort), no manual fixes. A pelican cycling by the sea in SVG: blue sky, clouds, ocean, beach and seagulls, a red bicycle (spokes and chain), and a pelican with a long beak and gular pouch."
|
||||
},
|
||||
"contributor": "Nagi-ovo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"label": "MiMo v2.5 Pro",
|
||||
"vendor": "Xiaomi",
|
||||
"harness": "Claude Code",
|
||||
"effort": "Max",
|
||||
"artifactDir": "mimo-v2-5-pro/claude-code-max",
|
||||
"order": 97,
|
||||
"runs": {
|
||||
"mythos-craft": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 MiMo v2.5 Pro(最高思考强度)一次生成;原始产出在浏览器标题、开屏「Generated by MiMo v2.5 Pro」字样与控制台日志中直接写出了模型名,会在 Arena 盲评中泄露身份,本页为去除这些身份字样后的版本(改 3 行),生成逻辑未改。游戏内容:Perlin 噪声地形、WASD + 鼠标第一人称操控、12 种方块类型(含水、黑曜石、水晶、金块等)、程序化材质纹理、四种神话建筑(水晶塔、石环阵、龙骨遗迹、天空遗迹),按 G 可依次传送至神话建筑,小地图显示玩家位置和地标。",
|
||||
"en": "Generated in one shot via Claude Code with MiMo v2.5 Pro (max thinking effort). The original one-shot output wrote the model name into the browser title, an on-screen \"Generated by MiMo v2.5 Pro\" caption, and a console log, which would reveal its identity in the blind Arena; this page strips those identity strings (3 lines changed) and leaves the generation logic untouched. Gameplay: Perlin noise terrain, WASD + mouse first-person controls, 12 block types (including water, obsidian, crystal, gold), procedural textures, and four mythic structures (Crystal Tower, Stone Circle, Dragon Skeleton, Sky Ruins). Press G to cycle-teleport between landmarks; minimap shows player position and landmarks."
|
||||
},
|
||||
"contributor": "Mintcolour"
|
||||
},
|
||||
"pelican-cycling": {
|
||||
"note": {
|
||||
"zh": "在 Claude Code 中以 MiMo v2.5 Pro(最高思考强度)一次生成;原始产出在 SVG 末尾有一行「Generated by MiMo v2.5 Pro」水印文本,会在 Arena 盲评中泄露身份,本页已移除该文本,其余未改。场景包含渐变天空、太阳、云朵、海洋波浪、沙滩、棕榈树、海鸥、完整自行车细节(辐条、链条、齿轮、脚踏)以及鹈鹕(长喙、喉囊、羽翼、踩踏姿态),海滩上有海星和贝壳装饰。",
|
||||
"en": "Generated in one shot via Claude Code with MiMo v2.5 Pro (max thinking effort). The original output carried a \"Generated by MiMo v2.5 Pro\" watermark text at the bottom of the SVG, which would reveal its identity in the blind Arena; this page removes that text and nothing else. Scene includes gradient sky, sun, clouds, ocean waves, beach, palm trees, seagulls, detailed bicycle (spokes, chain, gears, pedals), and a pelican (beak, gular pouch, wings, pedaling pose), with starfish and shell decorations on the beach."
|
||||
},
|
||||
"contributor": "Mintcolour"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "cs2-dust2-sim",
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"three": "^0.170.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/three": "^0.170.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.11",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="],
|
||||
|
||||
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.2", "", { "os": "android", "cpu": "arm" }, "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.62.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.62.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.62.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.62.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.62.2", "", { "os": "linux", "cpu": "arm" }, "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.62.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.62.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.62.2", "", { "os": "linux", "cpu": "none" }, "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.62.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.62.2", "", { "os": "linux", "cpu": "x64" }, "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg=="],
|
||||
|
||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.62.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.62.2", "", { "os": "none", "cpu": "arm64" }, "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.62.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.62.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.2", "", { "os": "win32", "cpu": "x64" }, "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA=="],
|
||||
|
||||
"@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="],
|
||||
|
||||
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
|
||||
|
||||
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
|
||||
|
||||
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
|
||||
|
||||
"@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="],
|
||||
|
||||
"@types/stats.js": ["@types/stats.js@0.17.4", "", {}, "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA=="],
|
||||
|
||||
"@types/three": ["@types/three@0.170.0", "", { "dependencies": { "@tweenjs/tween.js": "~23.1.3", "@types/stats.js": "*", "@types/webxr": "*", "@webgpu/types": "*", "fflate": "~0.8.2", "meshoptimizer": "~0.18.1" } }, "sha512-CUm2uckq+zkCY7ZbFpviRttY+6f9fvwm6YqSqPfA5K22s9w7R4VnA3rzJse8kHVvuzLcTx+CjNCs2NYe0QFAyg=="],
|
||||
|
||||
"@types/webxr": ["@types/webxr@0.5.24", "", {}, "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
|
||||
"@webgpu/types": ["@webgpu/types@0.1.71", "", {}, "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.43", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001805", "", {}, "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA=="],
|
||||
|
||||
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.389", "", {}, "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg=="],
|
||||
|
||||
"esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
|
||||
|
||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
|
||||
|
||||
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"meshoptimizer": ["meshoptimizer@0.18.1", "", {}, "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
||||
|
||||
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
||||
|
||||
"react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="],
|
||||
|
||||
"react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="],
|
||||
|
||||
"rollup": ["rollup@4.62.2", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.62.2", "@rollup/rollup-android-arm64": "4.62.2", "@rollup/rollup-darwin-arm64": "4.62.2", "@rollup/rollup-darwin-x64": "4.62.2", "@rollup/rollup-freebsd-arm64": "4.62.2", "@rollup/rollup-freebsd-x64": "4.62.2", "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", "@rollup/rollup-linux-arm-musleabihf": "4.62.2", "@rollup/rollup-linux-arm64-gnu": "4.62.2", "@rollup/rollup-linux-arm64-musl": "4.62.2", "@rollup/rollup-linux-loong64-gnu": "4.62.2", "@rollup/rollup-linux-loong64-musl": "4.62.2", "@rollup/rollup-linux-ppc64-gnu": "4.62.2", "@rollup/rollup-linux-ppc64-musl": "4.62.2", "@rollup/rollup-linux-riscv64-gnu": "4.62.2", "@rollup/rollup-linux-riscv64-musl": "4.62.2", "@rollup/rollup-linux-s390x-gnu": "4.62.2", "@rollup/rollup-linux-x64-gnu": "4.62.2", "@rollup/rollup-linux-x64-musl": "4.62.2", "@rollup/rollup-openbsd-x64": "4.62.2", "@rollup/rollup-openharmony-arm64": "4.62.2", "@rollup/rollup-win32-arm64-msvc": "4.62.2", "@rollup/rollup-win32-ia32-msvc": "4.62.2", "@rollup/rollup-win32-x64-gnu": "4.62.2", "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA=="],
|
||||
|
||||
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"three": ["three@0.170.0", "", {}, "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
|
||||
|
||||
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DUST II — 5v5 模拟器</title>
|
||||
<style>
|
||||
html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #0b0e12; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"name": "cs2-dust2-sim",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"three": "^0.170.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/three": "^0.170.0",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.11"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Engine } from './game/engine'
|
||||
import HUD from './ui/HUD'
|
||||
|
||||
export default function App() {
|
||||
const viewportRef = useRef<HTMLDivElement>(null)
|
||||
const [engine, setEngine] = useState<Engine | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const e = new Engine(viewportRef.current!)
|
||||
setEngine(e)
|
||||
;(window as unknown as { __engine?: Engine }).__engine = e
|
||||
if (new URLSearchParams(location.search).has('auto')) e.start('CT', true)
|
||||
return () => e.dispose()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<div ref={viewportRef} className="viewport" />
|
||||
{engine && <HUD engine={engine} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import type { GameMap } from './map'
|
||||
import type { Nav } from './nav'
|
||||
import type { PhysicsWorld } from './physics'
|
||||
import type { Soldier } from './soldier'
|
||||
import { EYE_HEIGHT } from './types'
|
||||
|
||||
export type Duty = 'none' | 'plant' | 'defuse' | 'pickup'
|
||||
|
||||
export interface Mission {
|
||||
anchors: THREE.Vector3[]
|
||||
final: THREE.Vector3
|
||||
look: THREE.Vector3 | null
|
||||
duty: Duty
|
||||
}
|
||||
|
||||
export interface AIWorld {
|
||||
map: GameMap
|
||||
nav: Nav
|
||||
physics: PhysicsWorld
|
||||
soldiers: Soldier[]
|
||||
time: number
|
||||
bombPlantedPos: THREE.Vector3 | null
|
||||
bombDroppedPos: THREE.Vector3 | null
|
||||
}
|
||||
|
||||
const TWO_PI = Math.PI * 2
|
||||
|
||||
function angleLerp(a: number, target: number, maxStep: number): number {
|
||||
let d = (target - a) % TWO_PI
|
||||
if (d > Math.PI) d -= TWO_PI
|
||||
if (d < -Math.PI) d += TWO_PI
|
||||
return a + THREE.MathUtils.clamp(d, -maxStep, maxStep)
|
||||
}
|
||||
|
||||
function angleDiff(a: number, b: number): number {
|
||||
let d = (b - a) % TWO_PI
|
||||
if (d > Math.PI) d -= TWO_PI
|
||||
if (d < -Math.PI) d += TWO_PI
|
||||
return d
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-soldier AI: waypoint navigation over the grid, FOV+LOS target acquisition
|
||||
* with reaction delay, burst-fire combat, plant/defuse/pickup duties.
|
||||
*/
|
||||
export class AIBrain {
|
||||
s: Soldier
|
||||
private skill: { err: number; react: number; aggro: number; headRate: number }
|
||||
|
||||
mission: Mission | null = null
|
||||
private missionIdx = 0
|
||||
private arrived = false
|
||||
|
||||
private path: THREE.Vector3[] | null = null
|
||||
private pathIdx = 0
|
||||
private pathGoal = new THREE.Vector3()
|
||||
private repathAt = 0
|
||||
|
||||
target: Soldier | null = null
|
||||
private targetSince = 0
|
||||
private reactUntil = 0
|
||||
private lastSeenPos = new THREE.Vector3()
|
||||
private lastSeenAt = -99
|
||||
private alertUntil = 0
|
||||
private alertPos: THREE.Vector3 | null = null
|
||||
|
||||
private nextThink = 0
|
||||
private burstUntil = 0
|
||||
private burstPauseUntil = 0
|
||||
private strafeDir = 0
|
||||
private strafeUntil = 0
|
||||
private scopeStart = 0
|
||||
private stuckPos = new THREE.Vector3()
|
||||
private stuckAt = 0
|
||||
private wanderAt = 0
|
||||
private lookYawTarget: number | null = null
|
||||
|
||||
constructor(s: Soldier) {
|
||||
this.s = s
|
||||
this.skill = {
|
||||
err: 1.1 + Math.random() * 0.9,
|
||||
react: 0.35 + Math.random() * 0.4,
|
||||
aggro: Math.random(),
|
||||
headRate: 0.04 + Math.random() * 0.1,
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.mission = null
|
||||
this.path = null
|
||||
this.target = null
|
||||
this.arrived = false
|
||||
this.lastSeenAt = -99
|
||||
this.alertPos = null
|
||||
this.lookYawTarget = null
|
||||
}
|
||||
|
||||
setMission(anchors: THREE.Vector3[], final: THREE.Vector3, look: THREE.Vector3 | null, duty: Duty) {
|
||||
this.mission = { anchors: [...anchors], final, look, duty }
|
||||
this.missionIdx = 0
|
||||
this.arrived = false
|
||||
this.path = null
|
||||
this.lookYawTarget = null
|
||||
}
|
||||
|
||||
heard(pos: THREE.Vector3, time: number) {
|
||||
if (this.target) return
|
||||
this.alertUntil = time + 3
|
||||
this.alertPos = pos.clone()
|
||||
}
|
||||
|
||||
update(dt: number, w: AIWorld) {
|
||||
const s = this.s
|
||||
const c = s.control
|
||||
c.moveX = 0; c.moveZ = 0
|
||||
c.fire = false; c.fireTap = false; c.altTap = false
|
||||
c.jump = false; c.use = false; c.reload = false; c.slot = null
|
||||
|
||||
if (!s.alive) return
|
||||
|
||||
if (w.time >= this.nextThink) {
|
||||
this.nextThink = w.time + 0.12 + Math.random() * 0.06
|
||||
this.think(w)
|
||||
}
|
||||
|
||||
if (this.target && w.time >= this.reactUntil) {
|
||||
this.combat(dt, w)
|
||||
} else if (this.target) {
|
||||
this.facePoint(this.target.pos, dt) // reacting: turn but hold fire
|
||||
} else if (this.lastSeenAt > w.time - 2.5 && this.skill.aggro > 0.35) {
|
||||
this.chase(dt, w)
|
||||
} else {
|
||||
this.missionMove(dt, w)
|
||||
}
|
||||
|
||||
this.dutyBehavior(w)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
private think(w: AIWorld) {
|
||||
const s = this.s
|
||||
|
||||
// perception
|
||||
let best: Soldier | null = null
|
||||
let bestDist = Infinity
|
||||
const eye = s.eyePos
|
||||
for (const e of w.soldiers) {
|
||||
if (!e.alive || e.team === s.team) continue
|
||||
const dist = e.pos.distanceTo(s.pos)
|
||||
if (dist > 70 || dist >= bestDist) continue
|
||||
const dirYaw = Math.atan2(-(e.pos.x - s.pos.x), -(e.pos.z - s.pos.z))
|
||||
const inFov = Math.abs(angleDiff(s.yaw, dirYaw)) < (w.time < this.alertUntil ? Math.PI : 0.95)
|
||||
if (!inFov && dist > 3) continue
|
||||
const targetEye = new THREE.Vector3(e.pos.x, e.pos.y + EYE_HEIGHT * 0.8, e.pos.z)
|
||||
if (!w.physics.hasLOS(eye, targetEye)) continue
|
||||
best = e
|
||||
bestDist = dist
|
||||
}
|
||||
|
||||
if (best) {
|
||||
if (this.target !== best) {
|
||||
if (!this.target) this.reactUntil = w.time + this.skill.react
|
||||
this.target = best
|
||||
this.targetSince = w.time
|
||||
}
|
||||
this.lastSeenPos.copy(best.pos)
|
||||
this.lastSeenAt = w.time
|
||||
best.lastSeenBy[this.s.team] = w.time
|
||||
} else if (this.target) {
|
||||
if (w.time - this.lastSeenAt > 0.5) this.target = null
|
||||
}
|
||||
|
||||
// reload when safe or empty
|
||||
const wpn = s.weapon
|
||||
if (!wpn.isKnife) {
|
||||
if (wpn.mag === 0 && (wpn.reserve > 0)) s.control.reload = true
|
||||
else if (!this.target && wpn.mag < wpn.def.magSize * 0.35 && wpn.reserve > 0) s.control.reload = true
|
||||
// completely dry: go knife
|
||||
if (wpn.mag === 0 && wpn.reserve === 0) {
|
||||
if (s.slot === 'primary' && s.weapons.secondary && (s.weapons.secondary.mag > 0 || s.weapons.secondary.reserve > 0)) s.control.slot = 'secondary'
|
||||
else s.control.slot = 'knife'
|
||||
}
|
||||
}
|
||||
|
||||
// stuck detection while pathing
|
||||
if (this.path && !this.arrived) {
|
||||
if (w.time - this.stuckAt > 1.3) {
|
||||
if (s.pos.distanceTo(this.stuckPos) < 0.35) {
|
||||
this.path = null // force repath
|
||||
this.repathAt = 0
|
||||
}
|
||||
this.stuckPos.copy(s.pos)
|
||||
this.stuckAt = w.time
|
||||
}
|
||||
} else {
|
||||
this.stuckPos.copy(s.pos)
|
||||
this.stuckAt = w.time
|
||||
}
|
||||
}
|
||||
|
||||
private combat(dt: number, w: AIWorld) {
|
||||
const s = this.s
|
||||
const t = this.target!
|
||||
const c = s.control
|
||||
const dist = t.pos.distanceTo(s.pos)
|
||||
const wpn = s.weapon
|
||||
|
||||
// knife rush when using knife
|
||||
if (wpn.isKnife) {
|
||||
this.moveToward(t.pos, dt, w, true)
|
||||
this.facePoint(t.pos, dt)
|
||||
if (dist < 2.0) c.fire = true
|
||||
return
|
||||
}
|
||||
|
||||
// aim: chest, occasionally head
|
||||
const settle = w.time - this.targetSince
|
||||
const aimY = Math.random() < this.skill.headRate && settle > 0.8 ? 1.62 : 1.15
|
||||
const aimPoint = new THREE.Vector3(t.pos.x, t.pos.y + aimY, t.pos.z)
|
||||
const errScale = this.skill.err * (0.45 + 0.55 * Math.exp(-settle * 1.1)) * (0.7 + dist / 55)
|
||||
const wobble = Math.sin(w.time * 7 + s.id * 3.1) * 0.02 * errScale
|
||||
aimPoint.x += wobble * dist * 0.5
|
||||
aimPoint.y += Math.cos(w.time * 6 + s.id) * 0.016 * errScale * dist * 0.5
|
||||
|
||||
const desiredYaw = Math.atan2(-(aimPoint.x - s.pos.x), -(aimPoint.z - s.pos.z))
|
||||
const flatDist = Math.hypot(aimPoint.x - s.pos.x, aimPoint.z - s.pos.z)
|
||||
const desiredPitch = Math.atan2(aimPoint.y - s.eyePos.y, flatDist)
|
||||
const turnRate = 6.5 * dt
|
||||
s.yaw = angleLerp(s.yaw, desiredYaw, turnRate)
|
||||
s.pitch = THREE.MathUtils.clamp(angleLerp(s.pitch, desiredPitch, turnRate), -1.5, 1.5)
|
||||
|
||||
const aligned = Math.abs(angleDiff(s.yaw, desiredYaw)) < 0.055 && Math.abs(s.pitch - desiredPitch) < 0.05
|
||||
|
||||
const isAwp = wpn.def.id === 'awp'
|
||||
if (isAwp) {
|
||||
if (!s.scoped && aligned) {
|
||||
c.altTap = true // scope in
|
||||
this.scopeStart = w.time
|
||||
}
|
||||
if (s.scoped && aligned && w.time - this.scopeStart > 0.75) c.fireTap = true
|
||||
return // AWP AI stands still
|
||||
}
|
||||
|
||||
// burst fire
|
||||
if (aligned && wpn.mag > 0 && !wpn.reloading) {
|
||||
if (w.time > this.burstPauseUntil) {
|
||||
if (this.burstUntil < w.time) {
|
||||
this.burstUntil = w.time + (wpn.def.auto ? 0.12 + Math.random() * 0.25 : 0.05)
|
||||
this.burstPauseUntil = this.burstUntil + 0.25 + Math.random() * 0.5
|
||||
}
|
||||
}
|
||||
if (w.time < this.burstUntil) {
|
||||
c.fire = true
|
||||
c.fireTap = true
|
||||
}
|
||||
}
|
||||
|
||||
// strafe jiggle for rifles at medium range
|
||||
if (dist > 6 && dist < 40 && w.time > this.strafeUntil) {
|
||||
this.strafeDir = Math.random() < 0.4 ? 0 : (Math.random() < 0.5 ? -1 : 1)
|
||||
this.strafeUntil = w.time + 0.5 + Math.random() * 0.6
|
||||
}
|
||||
c.moveX = this.strafeDir * 0.6
|
||||
if (dist > 45) c.moveZ = 0.7 // push closer at long range
|
||||
}
|
||||
|
||||
private chase(dt: number, w: AIWorld) {
|
||||
this.moveToward(this.lastSeenPos, dt, w, false)
|
||||
this.facePoint(this.lastSeenPos, dt)
|
||||
if (this.s.pos.distanceTo(this.lastSeenPos) < 2.5) this.lastSeenAt = -99
|
||||
}
|
||||
|
||||
private missionMove(dt: number, w: AIWorld) {
|
||||
const s = this.s
|
||||
|
||||
// face heard noises while holding
|
||||
if (this.arrived) {
|
||||
if (this.alertPos && w.time < this.alertUntil) {
|
||||
this.facePoint(this.alertPos, dt)
|
||||
} else if (this.mission?.look) {
|
||||
this.facePoint(this.mission.look, dt)
|
||||
} else if (this.lookYawTarget !== null) {
|
||||
s.yaw = angleLerp(s.yaw, this.lookYawTarget, 3 * dt)
|
||||
}
|
||||
// occasional gaze wander + micro reposition
|
||||
if (w.time > this.wanderAt) {
|
||||
this.wanderAt = w.time + 3 + Math.random() * 4
|
||||
this.lookYawTarget = s.yaw + (Math.random() - 0.5) * 1.6
|
||||
if (this.mission?.look && Math.random() < 0.6) this.lookYawTarget = null
|
||||
}
|
||||
s.pitch = angleLerp(s.pitch, 0, 2 * dt)
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.mission) return
|
||||
const waypoint = this.missionIdx < this.mission.anchors.length
|
||||
? this.mission.anchors[this.missionIdx]
|
||||
: this.mission.final
|
||||
|
||||
if (this.moveToward(waypoint, dt, w, false)) {
|
||||
if (this.missionIdx < this.mission.anchors.length) {
|
||||
this.missionIdx++
|
||||
this.path = null
|
||||
} else {
|
||||
this.arrived = true
|
||||
this.path = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Steers along an A* path toward `goal`. Returns true when within reach. */
|
||||
private moveToward(goal: THREE.Vector3, dt: number, w: AIWorld, sprint: boolean): boolean {
|
||||
const s = this.s
|
||||
const dGoal = Math.hypot(goal.x - s.pos.x, goal.z - s.pos.z)
|
||||
if (dGoal < 1.0) return true
|
||||
|
||||
if (!this.path || this.pathGoal.distanceTo(goal) > 2 || w.time > this.repathAt) {
|
||||
this.path = w.nav.findPath(s.pos, goal)
|
||||
this.pathIdx = 0
|
||||
this.pathGoal.copy(goal)
|
||||
this.repathAt = w.time + 3 + Math.random()
|
||||
if (!this.path || this.path.length === 0) return true
|
||||
}
|
||||
|
||||
let wp = this.path[this.pathIdx]
|
||||
while (wp && Math.hypot(wp.x - s.pos.x, wp.z - s.pos.z) < 0.7) {
|
||||
this.pathIdx++
|
||||
if (this.pathIdx >= this.path.length) return true
|
||||
wp = this.path[this.pathIdx]
|
||||
}
|
||||
if (!wp) return true
|
||||
|
||||
const dir = new THREE.Vector3(wp.x - s.pos.x, 0, wp.z - s.pos.z).normalize()
|
||||
// convert world dir to local move axes
|
||||
const fwd = new THREE.Vector3(-Math.sin(s.yaw), 0, -Math.cos(s.yaw))
|
||||
const right = s.rightDir()
|
||||
s.control.moveZ = dir.dot(fwd)
|
||||
s.control.moveX = dir.dot(right)
|
||||
|
||||
if (!this.target) {
|
||||
const wantYaw = Math.atan2(-dir.x, -dir.z)
|
||||
s.yaw = angleLerp(s.yaw, wantYaw, (sprint ? 10 : 6) * dt)
|
||||
s.pitch = angleLerp(s.pitch, 0, 3 * dt)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private facePoint(p: THREE.Vector3, dt: number) {
|
||||
const s = this.s
|
||||
const wantYaw = Math.atan2(-(p.x - s.pos.x), -(p.z - s.pos.z))
|
||||
s.yaw = angleLerp(s.yaw, wantYaw, 8 * dt)
|
||||
}
|
||||
|
||||
private dutyBehavior(w: AIWorld) {
|
||||
const s = this.s
|
||||
if (!this.mission) return
|
||||
const c = s.control
|
||||
|
||||
switch (this.mission.duty) {
|
||||
case 'plant': {
|
||||
if (!s.hasBomb) break
|
||||
if (this.target) break // fight first
|
||||
if (w.map.plantSiteAt(s.pos.x, s.pos.z) && this.arrived && s.onGround) {
|
||||
c.use = true
|
||||
c.moveX = 0; c.moveZ = 0
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'defuse': {
|
||||
if (!w.bombPlantedPos) break
|
||||
const d = Math.hypot(w.bombPlantedPos.x - s.pos.x, w.bombPlantedPos.z - s.pos.z)
|
||||
if (d < 1.3 && (!this.target || w.time - this.targetSince < 0.1)) {
|
||||
c.use = true
|
||||
c.moveX = 0; c.moveZ = 0
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'pickup':
|
||||
break // walking over the bomb picks it up (engine)
|
||||
case 'none':
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
|
||||
type ShotSound = 'ak' | 'm4' | 'awp' | 'pistol' | 'glock' | 'deagle' | 'knife'
|
||||
|
||||
/**
|
||||
* All game audio is synthesized with the Web Audio API — no audio files.
|
||||
* World sounds are attenuated by distance and stereo-panned relative to the camera.
|
||||
*/
|
||||
export class AudioMan {
|
||||
private ctx: AudioContext | null = null
|
||||
private master: GainNode | null = null
|
||||
private noise: AudioBuffer | null = null
|
||||
private listenerPos = new THREE.Vector3()
|
||||
private listenerRight = new THREE.Vector3(1, 0, 0)
|
||||
|
||||
resume() {
|
||||
if (!this.ctx) {
|
||||
this.ctx = new AudioContext()
|
||||
this.master = this.ctx.createGain()
|
||||
this.master.gain.value = 0.5
|
||||
this.master.connect(this.ctx.destination)
|
||||
const len = this.ctx.sampleRate
|
||||
this.noise = this.ctx.createBuffer(1, len, this.ctx.sampleRate)
|
||||
const data = this.noise.getChannelData(0)
|
||||
for (let i = 0; i < len; i++) data[i] = Math.random() * 2 - 1
|
||||
}
|
||||
if (this.ctx.state === 'suspended') void this.ctx.resume()
|
||||
}
|
||||
|
||||
setListener(pos: THREE.Vector3, right: THREE.Vector3) {
|
||||
this.listenerPos.copy(pos)
|
||||
this.listenerRight.copy(right)
|
||||
}
|
||||
|
||||
private out(pos: THREE.Vector3 | null, gain: number): GainNode | null {
|
||||
if (!this.ctx || !this.master) return null
|
||||
const g = this.ctx.createGain()
|
||||
if (pos) {
|
||||
const d = pos.distanceTo(this.listenerPos)
|
||||
const att = 1 / (1 + d * 0.05)
|
||||
g.gain.value = gain * att
|
||||
const dir = new THREE.Vector3().subVectors(pos, this.listenerPos)
|
||||
const dl = dir.length()
|
||||
const pan = this.ctx.createStereoPanner()
|
||||
pan.pan.value = dl > 0.5 ? THREE.MathUtils.clamp(dir.dot(this.listenerRight) / dl, -0.85, 0.85) : 0
|
||||
g.connect(pan)
|
||||
pan.connect(this.master)
|
||||
} else {
|
||||
g.gain.value = gain
|
||||
g.connect(this.master)
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
private playNoise(opts: {
|
||||
pos: THREE.Vector3 | null, gain: number, dur: number,
|
||||
filter?: BiquadFilterType, freq?: number, q?: number, freqEnd?: number,
|
||||
rate?: number, delay?: number,
|
||||
}) {
|
||||
if (!this.ctx || !this.noise) return
|
||||
const dst = this.out(opts.pos, 1)
|
||||
if (!dst) return
|
||||
const t0 = this.ctx.currentTime + (opts.delay ?? 0)
|
||||
const src = this.ctx.createBufferSource()
|
||||
src.buffer = this.noise
|
||||
src.loop = true
|
||||
src.playbackRate.value = opts.rate ?? 1
|
||||
const env = this.ctx.createGain()
|
||||
env.gain.setValueAtTime(opts.gain, t0)
|
||||
env.gain.exponentialRampToValueAtTime(0.001, t0 + opts.dur)
|
||||
let node: AudioNode = src
|
||||
if (opts.filter) {
|
||||
const f = this.ctx.createBiquadFilter()
|
||||
f.type = opts.filter
|
||||
f.frequency.setValueAtTime(opts.freq ?? 1000, t0)
|
||||
if (opts.freqEnd) f.frequency.exponentialRampToValueAtTime(opts.freqEnd, t0 + opts.dur)
|
||||
f.Q.value = opts.q ?? 1
|
||||
node.connect(f)
|
||||
node = f
|
||||
}
|
||||
node.connect(env)
|
||||
env.connect(dst)
|
||||
src.start(t0, Math.random() * 0.5)
|
||||
src.stop(t0 + opts.dur + 0.05)
|
||||
}
|
||||
|
||||
private playTone(opts: {
|
||||
pos: THREE.Vector3 | null, gain: number, dur: number,
|
||||
freq: number, freqEnd?: number, type?: OscillatorType, delay?: number,
|
||||
}) {
|
||||
if (!this.ctx) return
|
||||
const dst = this.out(opts.pos, 1)
|
||||
if (!dst) return
|
||||
const t0 = this.ctx.currentTime + (opts.delay ?? 0)
|
||||
const osc = this.ctx.createOscillator()
|
||||
osc.type = opts.type ?? 'sine'
|
||||
osc.frequency.setValueAtTime(opts.freq, t0)
|
||||
if (opts.freqEnd) osc.frequency.exponentialRampToValueAtTime(Math.max(1, opts.freqEnd), t0 + opts.dur)
|
||||
const env = this.ctx.createGain()
|
||||
env.gain.setValueAtTime(opts.gain, t0)
|
||||
env.gain.exponentialRampToValueAtTime(0.001, t0 + opts.dur)
|
||||
osc.connect(env)
|
||||
env.connect(dst)
|
||||
osc.start(t0)
|
||||
osc.stop(t0 + opts.dur + 0.05)
|
||||
}
|
||||
|
||||
shot(kind: ShotSound, pos: THREE.Vector3 | null) {
|
||||
const r = 0.94 + Math.random() * 0.12
|
||||
switch (kind) {
|
||||
case 'ak':
|
||||
this.playTone({ pos, gain: 0.55, dur: 0.1, freq: 165 * r, freqEnd: 85, type: 'square' })
|
||||
this.playNoise({ pos, gain: 0.5, dur: 0.11, filter: 'bandpass', freq: 1300, q: 0.7, rate: r })
|
||||
this.playNoise({ pos, gain: 0.3, dur: 0.025, filter: 'highpass', freq: 4800 })
|
||||
break
|
||||
case 'm4':
|
||||
this.playTone({ pos, gain: 0.4, dur: 0.06, freq: 210 * r, freqEnd: 120, type: 'square' })
|
||||
this.playNoise({ pos, gain: 0.42, dur: 0.07, filter: 'bandpass', freq: 2100, q: 0.8, rate: r })
|
||||
this.playNoise({ pos, gain: 0.24, dur: 0.02, filter: 'highpass', freq: 6000 })
|
||||
break
|
||||
case 'awp':
|
||||
this.playTone({ pos, gain: 0.9, dur: 0.35, freq: 92 * r, freqEnd: 42, type: 'sine' })
|
||||
this.playNoise({ pos, gain: 0.85, dur: 0.32, filter: 'lowpass', freq: 900, freqEnd: 160, rate: r })
|
||||
this.playNoise({ pos, gain: 0.45, dur: 0.05, filter: 'highpass', freq: 3000 })
|
||||
break
|
||||
case 'deagle':
|
||||
this.playTone({ pos, gain: 0.7, dur: 0.13, freq: 135 * r, freqEnd: 65, type: 'square' })
|
||||
this.playNoise({ pos, gain: 0.55, dur: 0.13, filter: 'bandpass', freq: 950, q: 0.7, rate: r })
|
||||
break
|
||||
case 'pistol':
|
||||
this.playTone({ pos, gain: 0.32, dur: 0.045, freq: 290 * r, freqEnd: 150, type: 'square' })
|
||||
this.playNoise({ pos, gain: 0.3, dur: 0.05, filter: 'bandpass', freq: 2400, q: 1, rate: r })
|
||||
break
|
||||
case 'glock':
|
||||
this.playTone({ pos, gain: 0.3, dur: 0.05, freq: 330 * r, freqEnd: 170, type: 'square' })
|
||||
this.playNoise({ pos, gain: 0.28, dur: 0.055, filter: 'bandpass', freq: 2800, q: 1, rate: r })
|
||||
break
|
||||
case 'knife':
|
||||
this.playNoise({ pos, gain: 0.18, dur: 0.12, filter: 'bandpass', freq: 1800, freqEnd: 600, q: 2 })
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
knifeHit(pos: THREE.Vector3 | null) {
|
||||
this.playNoise({ pos, gain: 0.35, dur: 0.08, filter: 'lowpass', freq: 700 })
|
||||
this.playTone({ pos, gain: 0.2, dur: 0.07, freq: 220, freqEnd: 90 })
|
||||
}
|
||||
|
||||
reload(pos: THREE.Vector3 | null, duration: number) {
|
||||
const click = (delay: number, freq: number) => {
|
||||
this.playTone({ pos, gain: 0.18, dur: 0.03, freq, type: 'square', delay })
|
||||
this.playNoise({ pos, gain: 0.12, dur: 0.03, filter: 'highpass', freq: 2500, delay })
|
||||
}
|
||||
click(0.05, 700)
|
||||
click(duration * 0.45, 500)
|
||||
click(duration * 0.85, 900)
|
||||
}
|
||||
|
||||
footstep(pos: THREE.Vector3 | null, vol: number) {
|
||||
this.playNoise({
|
||||
pos, gain: 0.16 * vol, dur: 0.055,
|
||||
filter: 'lowpass', freq: 420 + Math.random() * 160, rate: 0.8 + Math.random() * 0.3,
|
||||
})
|
||||
}
|
||||
|
||||
scope(on: boolean) {
|
||||
this.playTone({ pos: null, gain: 0.12, dur: 0.05, freq: on ? 1400 : 900, freqEnd: on ? 1900 : 600, type: 'sine' })
|
||||
}
|
||||
|
||||
drawWeapon() {
|
||||
this.playNoise({ pos: null, gain: 0.1, dur: 0.04, filter: 'highpass', freq: 2000 })
|
||||
this.playTone({ pos: null, gain: 0.08, dur: 0.03, freq: 600, type: 'square', delay: 0.03 })
|
||||
}
|
||||
|
||||
hit(headshot: boolean) {
|
||||
if (headshot) {
|
||||
this.playTone({ pos: null, gain: 0.25, dur: 0.1, freq: 1700, freqEnd: 1400, type: 'triangle' })
|
||||
this.playTone({ pos: null, gain: 0.15, dur: 0.12, freq: 2550, freqEnd: 2100, type: 'sine', delay: 0.01 })
|
||||
} else {
|
||||
this.playTone({ pos: null, gain: 0.18, dur: 0.05, freq: 1150, freqEnd: 900, type: 'triangle' })
|
||||
}
|
||||
}
|
||||
|
||||
kill() {
|
||||
this.playTone({ pos: null, gain: 0.2, dur: 0.07, freq: 880, type: 'sine' })
|
||||
this.playTone({ pos: null, gain: 0.2, dur: 0.1, freq: 1320, type: 'sine', delay: 0.07 })
|
||||
}
|
||||
|
||||
death(pos: THREE.Vector3 | null) {
|
||||
this.playNoise({ pos, gain: 0.3, dur: 0.2, filter: 'lowpass', freq: 500, freqEnd: 120 })
|
||||
}
|
||||
|
||||
damage() {
|
||||
this.playNoise({ pos: null, gain: 0.2, dur: 0.09, filter: 'lowpass', freq: 800 })
|
||||
}
|
||||
|
||||
bombBeep(pos: THREE.Vector3 | null, urgent: boolean) {
|
||||
this.playTone({ pos, gain: urgent ? 0.3 : 0.22, dur: 0.07, freq: urgent ? 2900 : 2500, type: 'square' })
|
||||
}
|
||||
|
||||
bombPlanted() {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
this.playTone({ pos: null, gain: 0.16, dur: 0.06, freq: 2100, type: 'square', delay: i * 0.11 })
|
||||
}
|
||||
}
|
||||
|
||||
plantTick(pos: THREE.Vector3 | null) {
|
||||
this.playTone({ pos, gain: 0.1, dur: 0.03, freq: 1600, type: 'square' })
|
||||
}
|
||||
|
||||
defuseTick(pos: THREE.Vector3 | null) {
|
||||
this.playNoise({ pos, gain: 0.14, dur: 0.04, filter: 'highpass', freq: 3200 })
|
||||
}
|
||||
|
||||
bombDefused() {
|
||||
this.playTone({ pos: null, gain: 0.22, dur: 0.12, freq: 900, type: 'sine' })
|
||||
this.playTone({ pos: null, gain: 0.22, dur: 0.2, freq: 1350, type: 'sine', delay: 0.12 })
|
||||
}
|
||||
|
||||
explosion(pos: THREE.Vector3 | null) {
|
||||
this.playNoise({ pos, gain: 1.4, dur: 1.3, filter: 'lowpass', freq: 2800, freqEnd: 60 })
|
||||
this.playTone({ pos, gain: 1.0, dur: 1.1, freq: 55, freqEnd: 24, type: 'sine' })
|
||||
this.playNoise({ pos, gain: 0.5, dur: 0.4, filter: 'bandpass', freq: 500, q: 0.5, delay: 0.06 })
|
||||
}
|
||||
|
||||
doorSlide(pos: THREE.Vector3 | null) {
|
||||
this.playNoise({ pos, gain: 0.16, dur: 0.35, filter: 'bandpass', freq: 300, freqEnd: 500, q: 2 })
|
||||
}
|
||||
|
||||
roundStart() {
|
||||
this.playTone({ pos: null, gain: 0.18, dur: 0.1, freq: 660, type: 'sine' })
|
||||
this.playTone({ pos: null, gain: 0.18, dur: 0.16, freq: 990, type: 'sine', delay: 0.1 })
|
||||
}
|
||||
|
||||
roundEnd(win: boolean) {
|
||||
if (win) {
|
||||
this.playTone({ pos: null, gain: 0.2, dur: 0.12, freq: 784, type: 'triangle' })
|
||||
this.playTone({ pos: null, gain: 0.2, dur: 0.12, freq: 988, type: 'triangle', delay: 0.12 })
|
||||
this.playTone({ pos: null, gain: 0.2, dur: 0.24, freq: 1319, type: 'triangle', delay: 0.24 })
|
||||
} else {
|
||||
this.playTone({ pos: null, gain: 0.2, dur: 0.16, freq: 494, type: 'triangle' })
|
||||
this.playTone({ pos: null, gain: 0.2, dur: 0.3, freq: 330, type: 'triangle', delay: 0.16 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import type { Team, WeaponId } from './types'
|
||||
|
||||
type GunKind = 'rifle' | 'awp' | 'pistol' | 'knife'
|
||||
|
||||
export function gunKindOf(id: WeaponId): GunKind {
|
||||
if (id === 'awp') return 'awp'
|
||||
if (id === 'ak47' || id === 'm4a4') return 'rifle'
|
||||
if (id === 'knife') return 'knife'
|
||||
return 'pistol'
|
||||
}
|
||||
|
||||
function mat(color: number): THREE.MeshStandardMaterial {
|
||||
return new THREE.MeshStandardMaterial({ color, roughness: 0.9 })
|
||||
}
|
||||
|
||||
function box(w: number, h: number, d: number, m: THREE.MeshStandardMaterial): THREE.Mesh {
|
||||
const mesh = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), m)
|
||||
mesh.castShadow = true
|
||||
return mesh
|
||||
}
|
||||
|
||||
/**
|
||||
* Procedural humanoid: separate head / torso / two arms / two legs,
|
||||
* built entirely from code. Faces -Z. Feet at origin.
|
||||
*/
|
||||
export class CharacterRig {
|
||||
root = new THREE.Group()
|
||||
private body = new THREE.Group()
|
||||
private legL: THREE.Mesh
|
||||
private legR: THREE.Mesh
|
||||
private head: THREE.Group
|
||||
private aim = new THREE.Group()
|
||||
private guns: Record<GunKind, THREE.Group>
|
||||
private bombPack: THREE.Mesh
|
||||
private mats: THREE.MeshStandardMaterial[] = []
|
||||
private walkPhase = 0
|
||||
private dead = false
|
||||
private deathT = 0
|
||||
private fallAxis = 1
|
||||
|
||||
constructor(team: Team) {
|
||||
const skin = mat(0xd8b094)
|
||||
const shirt = team === 'CT' ? mat(0x3d5a73) : mat(0xa08752)
|
||||
const pants = team === 'CT' ? mat(0x2c3e50) : mat(0x55483a)
|
||||
const gear = team === 'CT' ? mat(0x232f3a) : mat(0x4a3b28)
|
||||
const headgear = team === 'CT' ? mat(0x46586b) : mat(0x7d3030)
|
||||
this.mats.push(skin, shirt, pants, gear, headgear)
|
||||
|
||||
// legs (pivot at hip)
|
||||
const mkLeg = (x: number) => {
|
||||
const leg = box(0.17, 0.9, 0.2, pants)
|
||||
leg.geometry.translate(0, -0.45, 0)
|
||||
leg.position.set(x, 0.9, 0)
|
||||
return leg
|
||||
}
|
||||
this.legL = mkLeg(-0.12)
|
||||
this.legR = mkLeg(0.12)
|
||||
this.body.add(this.legL, this.legR)
|
||||
|
||||
// torso + vest
|
||||
const torso = box(0.46, 0.52, 0.24, shirt)
|
||||
torso.position.y = 1.21
|
||||
const vest = box(0.5, 0.3, 0.28, gear)
|
||||
vest.position.y = 1.14
|
||||
this.body.add(torso, vest)
|
||||
|
||||
// bomb backpack (visible when carrying C4)
|
||||
this.bombPack = box(0.3, 0.36, 0.16, mat(0x8a2e2e))
|
||||
this.bombPack.position.set(0, 1.22, 0.22)
|
||||
this.bombPack.visible = false
|
||||
this.body.add(this.bombPack)
|
||||
|
||||
// head (pivot at neck)
|
||||
this.head = new THREE.Group()
|
||||
this.head.position.y = 1.5
|
||||
const skull = box(0.26, 0.26, 0.26, skin)
|
||||
skull.position.y = 0.16
|
||||
this.head.add(skull)
|
||||
if (team === 'CT') {
|
||||
const helmet = box(0.3, 0.13, 0.3, headgear)
|
||||
helmet.position.y = 0.28
|
||||
this.head.add(helmet)
|
||||
const visor = box(0.22, 0.06, 0.02, mat(0x111111))
|
||||
visor.position.set(0, 0.19, -0.14)
|
||||
this.head.add(visor)
|
||||
} else {
|
||||
const wrap = box(0.28, 0.1, 0.28, headgear)
|
||||
wrap.position.y = 0.27
|
||||
this.head.add(wrap)
|
||||
const tail = box(0.06, 0.16, 0.03, headgear)
|
||||
tail.position.set(0.08, 0.18, 0.15)
|
||||
this.head.add(tail)
|
||||
}
|
||||
this.body.add(this.head)
|
||||
|
||||
// aim group: both arms + weapon, pitches with view
|
||||
this.aim.position.y = 1.42
|
||||
const mkArm = (x: number, rotX: number, rotZ: number) => {
|
||||
const arm = box(0.13, 0.42, 0.13, shirt)
|
||||
arm.geometry.translate(0, -0.21, 0)
|
||||
const hand = box(0.09, 0.1, 0.09, skin)
|
||||
hand.position.y = -0.44
|
||||
arm.add(hand)
|
||||
arm.position.set(x, 0, 0)
|
||||
arm.rotation.set(rotX, 0, rotZ)
|
||||
return arm
|
||||
}
|
||||
this.aim.add(mkArm(-0.28, 1.15, 0.4), mkArm(0.28, 1.05, -0.3))
|
||||
|
||||
// world-model guns
|
||||
this.guns = {
|
||||
rifle: this.buildRifle(),
|
||||
awp: this.buildAwp(),
|
||||
pistol: this.buildPistol(),
|
||||
knife: this.buildKnife(),
|
||||
}
|
||||
for (const g of Object.values(this.guns)) {
|
||||
g.position.set(0.05, -0.24, -0.35)
|
||||
g.visible = false
|
||||
this.aim.add(g)
|
||||
}
|
||||
this.guns.rifle.visible = true
|
||||
this.body.add(this.aim)
|
||||
this.root.add(this.body)
|
||||
}
|
||||
|
||||
private buildRifle(): THREE.Group {
|
||||
const g = new THREE.Group()
|
||||
const metal = mat(0x3a3a3a)
|
||||
const wood = mat(0x6b4a2f)
|
||||
const body = box(0.06, 0.1, 0.55, metal); body.position.z = -0.15
|
||||
const barrel = box(0.035, 0.035, 0.35, metal); barrel.position.z = -0.55
|
||||
const stock = box(0.05, 0.09, 0.22, wood); stock.position.z = 0.2
|
||||
const magM = box(0.04, 0.16, 0.09, wood); magM.position.set(0, -0.12, -0.1); magM.rotation.x = 0.35
|
||||
g.add(body, barrel, stock, magM)
|
||||
return g
|
||||
}
|
||||
|
||||
private buildAwp(): THREE.Group {
|
||||
const g = new THREE.Group()
|
||||
const olive = mat(0x4a5d3a)
|
||||
const metal = mat(0x2e2e2e)
|
||||
const body = box(0.06, 0.1, 0.7, olive); body.position.z = -0.2
|
||||
const barrel = box(0.03, 0.03, 0.5, metal); barrel.position.z = -0.78
|
||||
const scope = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.035, 0.28, 8), metal)
|
||||
scope.rotation.x = Math.PI / 2
|
||||
scope.position.set(0, 0.08, -0.2)
|
||||
scope.castShadow = true
|
||||
const stock = box(0.05, 0.11, 0.24, olive); stock.position.z = 0.25
|
||||
g.add(body, barrel, scope, stock)
|
||||
return g
|
||||
}
|
||||
|
||||
private buildPistol(): THREE.Group {
|
||||
const g = new THREE.Group()
|
||||
const metal = mat(0x333338)
|
||||
const slide = box(0.045, 0.06, 0.24, metal); slide.position.z = -0.06
|
||||
const grip = box(0.04, 0.13, 0.06, metal); grip.position.set(0, -0.08, 0.05); grip.rotation.x = 0.25
|
||||
g.add(slide, grip)
|
||||
return g
|
||||
}
|
||||
|
||||
private buildKnife(): THREE.Group {
|
||||
const g = new THREE.Group()
|
||||
const blade = box(0.015, 0.045, 0.3, mat(0xcfd6dd)); blade.position.z = -0.15
|
||||
const handle = box(0.03, 0.05, 0.12, mat(0x2f2f33)); handle.position.z = 0.06
|
||||
g.add(blade, handle)
|
||||
return g
|
||||
}
|
||||
|
||||
setWeapon(id: WeaponId) {
|
||||
const kind = gunKindOf(id)
|
||||
for (const [k, g] of Object.entries(this.guns)) g.visible = k === kind
|
||||
}
|
||||
|
||||
setBomb(visible: boolean) {
|
||||
this.bombPack.visible = visible
|
||||
}
|
||||
|
||||
spawnReset() {
|
||||
this.dead = false
|
||||
this.deathT = 0
|
||||
this.walkPhase = 0
|
||||
this.body.rotation.set(0, 0, 0)
|
||||
this.body.position.set(0, 0, 0)
|
||||
for (const m of this.mats) { m.transparent = false; m.opacity = 1 }
|
||||
this.root.visible = true
|
||||
}
|
||||
|
||||
die() {
|
||||
if (this.dead) return
|
||||
this.dead = true
|
||||
this.deathT = 0
|
||||
this.fallAxis = Math.random() < 0.5 ? 1 : -1
|
||||
}
|
||||
|
||||
update(dt: number, pos: THREE.Vector3, yaw: number, pitch: number, speed: number, onGround: boolean) {
|
||||
this.root.position.copy(pos)
|
||||
this.root.rotation.y = yaw
|
||||
|
||||
if (this.dead) {
|
||||
this.deathT += dt
|
||||
const k = Math.min(1, this.deathT / 0.4)
|
||||
this.body.rotation.z = this.fallAxis * (Math.PI / 2) * k * k * (3 - 2 * k)
|
||||
this.body.position.y = 0.12 * k
|
||||
if (this.deathT > 6) {
|
||||
const fade = Math.max(0, 1 - (this.deathT - 6) / 1.5)
|
||||
for (const m of this.mats) { m.transparent = true; m.opacity = fade }
|
||||
if (fade <= 0) this.root.visible = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const speedRatio = Math.min(1, speed / 4.7)
|
||||
this.walkPhase += speed * dt * 2.2
|
||||
const swing = onGround ? Math.sin(this.walkPhase) * 0.7 * speedRatio : 0.25
|
||||
this.legL.rotation.x = swing
|
||||
this.legR.rotation.x = -swing
|
||||
this.body.position.y = onGround ? Math.abs(Math.sin(this.walkPhase)) * 0.045 * speedRatio : 0.05
|
||||
this.aim.rotation.x = pitch
|
||||
this.head.rotation.x = pitch * 0.55
|
||||
}
|
||||
|
||||
dispose(scene: THREE.Scene) {
|
||||
scene.remove(this.root)
|
||||
this.root.traverse(o => {
|
||||
if (o instanceof THREE.Mesh) o.geometry.dispose()
|
||||
})
|
||||
for (const m of this.mats) m.dispose()
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
|
||||
const MAX_TRACERS = 96
|
||||
const MAX_SPARKS = 768
|
||||
const MAX_BLOOD = 384
|
||||
|
||||
interface Particle { life: number; max: number; vx: number; vy: number; vz: number; gravity: number }
|
||||
|
||||
class ParticlePool {
|
||||
points: THREE.Points
|
||||
pos: Float32Array
|
||||
col: Float32Array
|
||||
parts: Particle[]
|
||||
baseCol: Float32Array
|
||||
private cursor = 0
|
||||
|
||||
constructor(count: number, size: number, additive: boolean) {
|
||||
this.pos = new Float32Array(count * 3).fill(-999)
|
||||
this.col = new Float32Array(count * 3)
|
||||
this.baseCol = new Float32Array(count * 3)
|
||||
this.parts = Array.from({ length: count }, () => ({ life: 0, max: 1, vx: 0, vy: 0, vz: 0, gravity: 0 }))
|
||||
const geo = new THREE.BufferGeometry()
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(this.pos, 3))
|
||||
geo.setAttribute('color', new THREE.BufferAttribute(this.col, 3))
|
||||
const matP = new THREE.PointsMaterial({
|
||||
size, vertexColors: true, transparent: true, depthWrite: false,
|
||||
blending: additive ? THREE.AdditiveBlending : THREE.NormalBlending,
|
||||
sizeAttenuation: true,
|
||||
})
|
||||
this.points = new THREE.Points(geo, matP)
|
||||
this.points.frustumCulled = false
|
||||
}
|
||||
|
||||
spawn(p: THREE.Vector3, vel: THREE.Vector3, life: number, color: THREE.Color, gravity: number) {
|
||||
const i = this.cursor
|
||||
this.cursor = (this.cursor + 1) % this.parts.length
|
||||
this.pos[i * 3] = p.x; this.pos[i * 3 + 1] = p.y; this.pos[i * 3 + 2] = p.z
|
||||
this.baseCol[i * 3] = color.r; this.baseCol[i * 3 + 1] = color.g; this.baseCol[i * 3 + 2] = color.b
|
||||
const part = this.parts[i]
|
||||
part.life = life; part.max = life
|
||||
part.vx = vel.x; part.vy = vel.y; part.vz = vel.z
|
||||
part.gravity = gravity
|
||||
}
|
||||
|
||||
update(dt: number) {
|
||||
for (let i = 0; i < this.parts.length; i++) {
|
||||
const p = this.parts[i]
|
||||
if (p.life <= 0) continue
|
||||
p.life -= dt
|
||||
if (p.life <= 0) {
|
||||
this.pos[i * 3 + 1] = -999
|
||||
this.col[i * 3] = this.col[i * 3 + 1] = this.col[i * 3 + 2] = 0
|
||||
continue
|
||||
}
|
||||
p.vy -= p.gravity * dt
|
||||
this.pos[i * 3] += p.vx * dt
|
||||
this.pos[i * 3 + 1] += p.vy * dt
|
||||
this.pos[i * 3 + 2] += p.vz * dt
|
||||
const f = p.life / p.max
|
||||
this.col[i * 3] = this.baseCol[i * 3] * f
|
||||
this.col[i * 3 + 1] = this.baseCol[i * 3 + 1] * f
|
||||
this.col[i * 3 + 2] = this.baseCol[i * 3 + 2] * f
|
||||
}
|
||||
this.points.geometry.attributes.position.needsUpdate = true
|
||||
this.points.geometry.attributes.color.needsUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
export class Effects {
|
||||
private scene: THREE.Scene
|
||||
private tracerGeo: THREE.BufferGeometry
|
||||
private tracerPos: Float32Array
|
||||
private tracerCol: Float32Array
|
||||
private tracers: { life: number; max: number }[]
|
||||
private tracerCursor = 0
|
||||
private sparks: ParticlePool
|
||||
private blood: ParticlePool
|
||||
private muzzleLight: THREE.PointLight
|
||||
private muzzleT = 0
|
||||
private explosionLight: THREE.PointLight
|
||||
private explosionT = 0
|
||||
private rings: { mesh: THREE.Mesh; life: number }[] = []
|
||||
shake = 0
|
||||
|
||||
constructor(scene: THREE.Scene) {
|
||||
this.scene = scene
|
||||
this.tracerPos = new Float32Array(MAX_TRACERS * 6).fill(-999)
|
||||
this.tracerCol = new Float32Array(MAX_TRACERS * 6)
|
||||
this.tracers = Array.from({ length: MAX_TRACERS }, () => ({ life: 0, max: 1 }))
|
||||
this.tracerGeo = new THREE.BufferGeometry()
|
||||
this.tracerGeo.setAttribute('position', new THREE.BufferAttribute(this.tracerPos, 3))
|
||||
this.tracerGeo.setAttribute('color', new THREE.BufferAttribute(this.tracerCol, 3))
|
||||
const lines = new THREE.LineSegments(this.tracerGeo, new THREE.LineBasicMaterial({
|
||||
vertexColors: true, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false,
|
||||
}))
|
||||
lines.frustumCulled = false
|
||||
scene.add(lines)
|
||||
|
||||
this.sparks = new ParticlePool(MAX_SPARKS, 0.09, true)
|
||||
this.blood = new ParticlePool(MAX_BLOOD, 0.11, false)
|
||||
scene.add(this.sparks.points, this.blood.points)
|
||||
|
||||
this.muzzleLight = new THREE.PointLight(0xffc070, 0, 14)
|
||||
this.explosionLight = new THREE.PointLight(0xff8030, 0, 60)
|
||||
scene.add(this.muzzleLight, this.explosionLight)
|
||||
}
|
||||
|
||||
tracer(a: THREE.Vector3, b: THREE.Vector3) {
|
||||
const i = this.tracerCursor
|
||||
this.tracerCursor = (this.tracerCursor + 1) % MAX_TRACERS
|
||||
this.tracerPos.set([a.x, a.y, a.z, b.x, b.y, b.z], i * 6)
|
||||
this.tracers[i].life = this.tracers[i].max = 0.07
|
||||
}
|
||||
|
||||
muzzle(pos: THREE.Vector3) {
|
||||
this.muzzleLight.position.copy(pos)
|
||||
this.muzzleLight.intensity = 6
|
||||
this.muzzleT = 0.05
|
||||
}
|
||||
|
||||
impact(pos: THREE.Vector3) {
|
||||
const c = new THREE.Color(0xffcc88)
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const vel = new THREE.Vector3((Math.random() - 0.5) * 5, Math.random() * 4, (Math.random() - 0.5) * 5)
|
||||
this.sparks.spawn(pos, vel, 0.22 + Math.random() * 0.18, c, 12)
|
||||
}
|
||||
}
|
||||
|
||||
bloodBurst(pos: THREE.Vector3) {
|
||||
const c = new THREE.Color(0x8a1515)
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const vel = new THREE.Vector3((Math.random() - 0.5) * 3.4, Math.random() * 2.6 - 0.4, (Math.random() - 0.5) * 3.4)
|
||||
this.blood.spawn(pos, vel, 0.3 + Math.random() * 0.25, c, 9)
|
||||
}
|
||||
}
|
||||
|
||||
explosion(pos: THREE.Vector3) {
|
||||
const fire = new THREE.Color(0xff9040)
|
||||
const smoke = new THREE.Color(0x553311)
|
||||
for (let i = 0; i < 130; i++) {
|
||||
const dir = new THREE.Vector3(Math.random() - 0.5, Math.random() * 0.8, Math.random() - 0.5).normalize()
|
||||
const sp = 4 + Math.random() * 16
|
||||
this.sparks.spawn(pos.clone().add(new THREE.Vector3(0, 0.5, 0)), dir.multiplyScalar(sp), 0.5 + Math.random() * 0.9, fire, 6)
|
||||
}
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const dir = new THREE.Vector3(Math.random() - 0.5, Math.random(), Math.random() - 0.5).normalize()
|
||||
this.blood.spawn(pos.clone(), dir.multiplyScalar(2 + Math.random() * 6), 1 + Math.random(), smoke, -1.5)
|
||||
}
|
||||
this.explosionLight.position.copy(pos).add(new THREE.Vector3(0, 2, 0))
|
||||
this.explosionLight.intensity = 80
|
||||
this.explosionT = 0.9
|
||||
this.shake = Math.max(this.shake, 1)
|
||||
|
||||
const ring = new THREE.Mesh(
|
||||
new THREE.RingGeometry(0.5, 1.2, 32),
|
||||
new THREE.MeshBasicMaterial({ color: 0xffbb66, transparent: true, opacity: 0.9, side: THREE.DoubleSide, depthWrite: false }),
|
||||
)
|
||||
ring.rotation.x = -Math.PI / 2
|
||||
ring.position.copy(pos).add(new THREE.Vector3(0, 0.3, 0))
|
||||
this.scene.add(ring)
|
||||
this.rings.push({ mesh: ring, life: 0.8 })
|
||||
}
|
||||
|
||||
update(dt: number) {
|
||||
for (let i = 0; i < MAX_TRACERS; i++) {
|
||||
const t = this.tracers[i]
|
||||
if (t.life <= 0) continue
|
||||
t.life -= dt
|
||||
const f = Math.max(0, t.life / t.max)
|
||||
const r = 1 * f, g2 = 0.85 * f, b = 0.5 * f
|
||||
this.tracerCol.set([r, g2, b, r * 0.6, g2 * 0.6, b * 0.6], i * 6)
|
||||
if (t.life <= 0) this.tracerPos.set([-999, -999, -999, -999, -999, -999], i * 6)
|
||||
}
|
||||
this.tracerGeo.attributes.position.needsUpdate = true
|
||||
this.tracerGeo.attributes.color.needsUpdate = true
|
||||
|
||||
this.sparks.update(dt)
|
||||
this.blood.update(dt)
|
||||
|
||||
this.muzzleT -= dt
|
||||
if (this.muzzleT <= 0) this.muzzleLight.intensity = 0
|
||||
if (this.explosionT > 0) {
|
||||
this.explosionT -= dt
|
||||
this.explosionLight.intensity = Math.max(0, 80 * (this.explosionT / 0.9))
|
||||
}
|
||||
for (let i = this.rings.length - 1; i >= 0; i--) {
|
||||
const r = this.rings[i]
|
||||
r.life -= dt
|
||||
const s = 1 + (0.8 - r.life) * 30
|
||||
r.mesh.scale.set(s, s, 1)
|
||||
;(r.mesh.material as THREE.MeshBasicMaterial).opacity = Math.max(0, r.life)
|
||||
if (r.life <= 0) {
|
||||
this.scene.remove(r.mesh)
|
||||
r.mesh.geometry.dispose()
|
||||
this.rings.splice(i, 1)
|
||||
}
|
||||
}
|
||||
this.shake *= Math.exp(-4 * dt)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,429 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||
import type { Team } from './types'
|
||||
|
||||
export const CELL = 2.5
|
||||
export const GRID = 52
|
||||
export const WALL_H = 4
|
||||
const HALF = GRID / 2 // world origin at grid center
|
||||
|
||||
export const enum Code { Wall = 0, Floor = 1, TSpawn = 2, CTSpawn = 3, ASite = 4, BSite = 5, Door = 6 }
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Layout: carved rectangles on a 52x52 grid (col, row; row 0 = north = -Z).
|
||||
// Regions: T spawn, CT spawn, Long A, A site, mid + mid doors, catwalk (short),
|
||||
// B tunnels, B site — wired up per the real Dust2 topology.
|
||||
// ---------------------------------------------------------------------------
|
||||
type Rect = [x1: number, y1: number, x2: number, y2: number, code: Code]
|
||||
|
||||
const RECTS: Rect[] = [
|
||||
[2, 2, 13, 12, Code.BSite], // B 点
|
||||
[6, 13, 9, 30, Code.Floor], // B洞 竖段
|
||||
[6, 28, 20, 31, Code.Floor], // B洞 横段
|
||||
[17, 31, 20, 41, Code.Floor], // B洞 → T 出生点
|
||||
[18, 42, 34, 49, Code.TSpawn], // T 出生点
|
||||
[24, 38, 29, 42, Code.Floor], // T → 中路
|
||||
[24, 10, 29, 38, Code.Floor], // 中路
|
||||
[22, 3, 34, 10, Code.CTSpawn], // CT 出生点
|
||||
[13, 10, 25, 13, Code.Floor], // B门通道 (CT ↔ B)
|
||||
[29, 20, 38, 23, Code.Floor], // 猫道横段
|
||||
[35, 10, 38, 23, Code.Floor], // A小 竖段
|
||||
[36, 2, 47, 9, Code.ASite], // A 点
|
||||
[44, 9, 47, 34, Code.Floor], // A大 竖段
|
||||
[30, 34, 47, 38, Code.Floor], // A大 外围横段
|
||||
[30, 38, 34, 42, Code.Floor], // T → A大
|
||||
[34, 3, 36, 7, Code.Floor], // CT → A 匝道
|
||||
]
|
||||
|
||||
// tunnels get ceilings so B洞 reads as an actual tunnel
|
||||
const TUNNEL_RECTS: Rect[] = [
|
||||
[6, 13, 9, 30, Code.Floor],
|
||||
[6, 28, 20, 31, Code.Floor],
|
||||
]
|
||||
|
||||
const CRATES: [c: number, r: number, stack: number][] = [
|
||||
// A site
|
||||
[40, 4, 2], [41, 4, 1], [40, 5, 1], [44, 7, 1], [37, 3, 1],
|
||||
// B site
|
||||
[5, 4, 1], [6, 4, 2], [10, 9, 1], [11, 9, 1], [4, 10, 1],
|
||||
// mid boxes
|
||||
[25, 34, 1], [26, 34, 2],
|
||||
// catwalk xbox
|
||||
[31, 21, 1],
|
||||
// long corner
|
||||
[46, 35, 1], [46, 36, 2],
|
||||
// tunnels + spawns
|
||||
[8, 21, 1], [20, 43, 1], [32, 4, 1],
|
||||
]
|
||||
|
||||
export interface AABB { min: THREE.Vector3; max: THREE.Vector3 }
|
||||
|
||||
export class DoorObj {
|
||||
center: THREE.Vector3
|
||||
totalW: number
|
||||
panelW: number
|
||||
open01 = 0
|
||||
target = 0
|
||||
panels: [THREE.Mesh, THREE.Mesh]
|
||||
group = new THREE.Group()
|
||||
|
||||
constructor(center: THREE.Vector3, totalW: number) {
|
||||
this.center = center
|
||||
this.totalW = totalW
|
||||
this.panelW = totalW / 2
|
||||
const mat = new THREE.MeshStandardMaterial({ color: 0x5c6e60, roughness: 0.7, metalness: 0.3 })
|
||||
const geo = new THREE.BoxGeometry(this.panelW, 3.2, 0.25)
|
||||
const left = new THREE.Mesh(geo, mat)
|
||||
const right = new THREE.Mesh(geo, mat)
|
||||
left.castShadow = right.castShadow = true
|
||||
this.panels = [left, right]
|
||||
this.group.add(left, right)
|
||||
// door frame header
|
||||
const header = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(totalW + 0.6, WALL_H - 3.2, 0.6),
|
||||
new THREE.MeshStandardMaterial({ color: 0x8f7f5c, roughness: 0.95 }),
|
||||
)
|
||||
header.position.set(center.x, 3.2 + (WALL_H - 3.2) / 2, center.z)
|
||||
this.group.add(header)
|
||||
this.layout()
|
||||
}
|
||||
|
||||
private layout() {
|
||||
const slide = this.open01 * (this.panelW + 0.3)
|
||||
this.panels[0].position.set(this.center.x - this.panelW / 2 - slide, 1.6, this.center.z)
|
||||
this.panels[1].position.set(this.center.x + this.panelW / 2 + slide, 1.6, this.center.z)
|
||||
}
|
||||
|
||||
/** Returns 'open' | 'close' when the door starts moving (for audio). */
|
||||
update(dt: number, positions: THREE.Vector3[]): 'open' | 'close' | null {
|
||||
let near = false
|
||||
for (const p of positions) {
|
||||
const dx = p.x - this.center.x, dz = p.z - this.center.z
|
||||
if (dx * dx + dz * dz < 4.2 * 4.2) { near = true; break }
|
||||
}
|
||||
const prevTarget = this.target
|
||||
this.target = near ? 1 : 0
|
||||
const before = this.open01
|
||||
const k = 1 - Math.exp(-5 * dt)
|
||||
this.open01 += (this.target - this.open01) * k
|
||||
if (Math.abs(this.open01 - before) > 1e-5) this.layout()
|
||||
if (prevTarget !== this.target) return this.target === 1 ? 'open' : 'close'
|
||||
return null
|
||||
}
|
||||
|
||||
colliders(): AABB[] {
|
||||
if (this.open01 > 0.92) return []
|
||||
const out: AABB[] = []
|
||||
for (const p of this.panels) {
|
||||
out.push({
|
||||
min: new THREE.Vector3(p.position.x - this.panelW / 2, 0, this.center.z - 0.2),
|
||||
max: new THREE.Vector3(p.position.x + this.panelW / 2, 3.2, this.center.z + 0.2),
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
export interface HoldSpot { pos: THREE.Vector3; look: THREE.Vector3 }
|
||||
|
||||
function makeFloorTexture(): THREE.CanvasTexture {
|
||||
const c = document.createElement('canvas')
|
||||
c.width = c.height = 256
|
||||
const g = c.getContext('2d')!
|
||||
g.fillStyle = '#c9b183'
|
||||
g.fillRect(0, 0, 256, 256)
|
||||
for (let i = 0; i < 2600; i++) {
|
||||
const v = Math.random()
|
||||
g.fillStyle = v < 0.5 ? 'rgba(120,98,64,0.14)' : 'rgba(240,226,190,0.12)'
|
||||
g.fillRect(Math.random() * 256, Math.random() * 256, 1 + Math.random() * 2.4, 1 + Math.random() * 2.4)
|
||||
}
|
||||
g.strokeStyle = 'rgba(90,74,50,0.20)'
|
||||
g.lineWidth = 1
|
||||
g.strokeRect(0.5, 0.5, 255, 255)
|
||||
const tex = new THREE.CanvasTexture(c)
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping
|
||||
tex.repeat.set(GRID, GRID)
|
||||
tex.colorSpace = THREE.SRGBColorSpace
|
||||
return tex
|
||||
}
|
||||
|
||||
function makeLetterTexture(text: string, color: string): THREE.CanvasTexture {
|
||||
const c = document.createElement('canvas')
|
||||
c.width = c.height = 128
|
||||
const g = c.getContext('2d')!
|
||||
g.clearRect(0, 0, 128, 128)
|
||||
g.font = 'bold 96px sans-serif'
|
||||
g.textAlign = 'center'
|
||||
g.textBaseline = 'middle'
|
||||
g.fillStyle = color
|
||||
g.fillText(text, 64, 68)
|
||||
const tex = new THREE.CanvasTexture(c)
|
||||
tex.colorSpace = THREE.SRGBColorSpace
|
||||
return tex
|
||||
}
|
||||
|
||||
export class GameMap {
|
||||
grid = new Uint8Array(GRID * GRID) // Code values
|
||||
crateCells = new Set<number>()
|
||||
crates: AABB[] = []
|
||||
doors: DoorObj[] = []
|
||||
group = new THREE.Group()
|
||||
spawns: Record<Team, { pos: THREE.Vector3; yaw: number }[]> = { CT: [], T: [] }
|
||||
anchors: Record<string, THREE.Vector3> = {}
|
||||
holdSpots: { A: HoldSpot[]; B: HoldSpot[]; mid: HoldSpot[] } = { A: [], B: [], mid: [] }
|
||||
routes: { A: THREE.Vector3[][]; B: THREE.Vector3[][] } = { A: [], B: [] }
|
||||
siteCells: { A: [number, number][]; B: [number, number][] } = { A: [], B: [] }
|
||||
|
||||
constructor() {
|
||||
this.buildGrid()
|
||||
this.buildVisuals()
|
||||
this.buildMeta()
|
||||
}
|
||||
|
||||
code(c: number, r: number): Code {
|
||||
if (c < 0 || r < 0 || c >= GRID || r >= GRID) return Code.Wall
|
||||
return this.grid[r * GRID + c] as Code
|
||||
}
|
||||
|
||||
isWall(c: number, r: number): boolean { return this.code(c, r) === Code.Wall }
|
||||
|
||||
isWalkableCell(c: number, r: number): boolean {
|
||||
return this.code(c, r) !== Code.Wall && !this.crateCells.has(r * GRID + c)
|
||||
}
|
||||
|
||||
cellCenter(c: number, r: number): THREE.Vector3 {
|
||||
return new THREE.Vector3((c - HALF + 0.5) * CELL, 0, (r - HALF + 0.5) * CELL)
|
||||
}
|
||||
|
||||
worldToCell(x: number, z: number): [number, number] {
|
||||
return [Math.floor(x / CELL + HALF), Math.floor(z / CELL + HALF)]
|
||||
}
|
||||
|
||||
plantSiteAt(x: number, z: number): 'A' | 'B' | null {
|
||||
const [c, r] = this.worldToCell(x, z)
|
||||
const code = this.code(c, r)
|
||||
if (code === Code.ASite) return 'A'
|
||||
if (code === Code.BSite) return 'B'
|
||||
return null
|
||||
}
|
||||
|
||||
private buildGrid() {
|
||||
for (const [x1, y1, x2, y2, code] of RECTS) {
|
||||
for (let r = y1; r <= y2; r++) {
|
||||
for (let c = x1; c <= x2; c++) this.grid[r * GRID + c] = code
|
||||
}
|
||||
}
|
||||
// 中门: frame + door cells at row 16
|
||||
this.grid[16 * GRID + 24] = Code.Wall
|
||||
this.grid[16 * GRID + 29] = Code.Wall
|
||||
for (let c = 25; c <= 28; c++) this.grid[16 * GRID + c] = Code.Door
|
||||
// A大双门 at row 32
|
||||
this.grid[32 * GRID + 44] = Code.Wall
|
||||
this.grid[32 * GRID + 47] = Code.Wall
|
||||
for (let c = 45; c <= 46; c++) this.grid[32 * GRID + c] = Code.Door
|
||||
|
||||
for (const [c, r, stack] of CRATES) {
|
||||
this.crateCells.add(r * GRID + c)
|
||||
const ctr = this.cellCenter(c, r)
|
||||
const s = 1.05
|
||||
this.crates.push({
|
||||
min: new THREE.Vector3(ctr.x - s, 0, ctr.z - s),
|
||||
max: new THREE.Vector3(ctr.x + s, 1.05 * stack, ctr.z + s),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private buildVisuals() {
|
||||
// floor
|
||||
const floorMat = new THREE.MeshStandardMaterial({ map: makeFloorTexture(), roughness: 1 })
|
||||
const floor = new THREE.Mesh(new THREE.PlaneGeometry(GRID * CELL, GRID * CELL), floorMat)
|
||||
floor.rotation.x = -Math.PI / 2
|
||||
floor.receiveShadow = true
|
||||
this.group.add(floor)
|
||||
|
||||
// walls: merge exposed wall cells into per-row runs
|
||||
const exposed = (c: number, r: number): boolean => {
|
||||
if (!this.isWall(c, r)) return false
|
||||
for (let dr = -1; dr <= 1; dr++) {
|
||||
for (let dc = -1; dc <= 1; dc++) {
|
||||
if (dc === 0 && dr === 0) continue
|
||||
if (this.code(c + dc, r + dr) !== Code.Wall) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
const geos: THREE.BufferGeometry[] = []
|
||||
const base = new THREE.Color('#c8ae7d')
|
||||
for (let r = 0; r < GRID; r++) {
|
||||
let c = 0
|
||||
while (c < GRID) {
|
||||
if (!exposed(c, r)) { c++; continue }
|
||||
let end = c
|
||||
while (end + 1 < GRID && exposed(end + 1, r)) end++
|
||||
const w = (end - c + 1) * CELL
|
||||
const g = new THREE.BoxGeometry(w, WALL_H, CELL)
|
||||
const cx = ((c + end + 1) / 2 - HALF) * CELL
|
||||
const cz = (r - HALF + 0.5) * CELL
|
||||
g.translate(cx, WALL_H / 2, cz)
|
||||
const col = base.clone().multiplyScalar(0.9 + Math.random() * 0.18)
|
||||
const n = g.attributes.position.count
|
||||
const colors = new Float32Array(n * 3)
|
||||
for (let i = 0; i < n; i++) { colors[i * 3] = col.r; colors[i * 3 + 1] = col.g; colors[i * 3 + 2] = col.b }
|
||||
g.setAttribute('color', new THREE.BufferAttribute(colors, 3))
|
||||
geos.push(g)
|
||||
c = end + 1
|
||||
}
|
||||
}
|
||||
const wallGeo = mergeGeometries(geos)!
|
||||
const walls = new THREE.Mesh(wallGeo, new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.95 }))
|
||||
walls.castShadow = true
|
||||
walls.receiveShadow = true
|
||||
this.group.add(walls)
|
||||
|
||||
// crates
|
||||
const crateMat = new THREE.MeshStandardMaterial({ color: 0x9a7743, roughness: 0.9 })
|
||||
const crateEdge = new THREE.MeshStandardMaterial({ color: 0x6e5430, roughness: 0.9 })
|
||||
for (const [c, r, stack] of CRATES) {
|
||||
const ctr = this.cellCenter(c, r)
|
||||
for (let s = 0; s < stack; s++) {
|
||||
const m = new THREE.Mesh(new THREE.BoxGeometry(2.1, 1.02, 2.1), crateMat)
|
||||
m.position.set(ctr.x, 0.525 + s * 1.05, ctr.z)
|
||||
m.castShadow = m.receiveShadow = true
|
||||
this.group.add(m)
|
||||
const trim = new THREE.Mesh(new THREE.BoxGeometry(2.16, 0.12, 2.16), crateEdge)
|
||||
trim.position.set(ctr.x, 1.0 + s * 1.05, ctr.z)
|
||||
this.group.add(trim)
|
||||
}
|
||||
}
|
||||
|
||||
// tunnel ceilings
|
||||
const ceilMat = new THREE.MeshStandardMaterial({ color: 0xa08a5f, roughness: 1 })
|
||||
for (const [x1, y1, x2, y2] of TUNNEL_RECTS) {
|
||||
const w = (x2 - x1 + 1) * CELL, d = (y2 - y1 + 1) * CELL
|
||||
const m = new THREE.Mesh(new THREE.BoxGeometry(w + CELL, 0.4, d + CELL), ceilMat)
|
||||
m.position.set(((x1 + x2 + 1) / 2 - HALF) * CELL, 3.3, ((y1 + y2 + 1) / 2 - HALF) * CELL)
|
||||
m.castShadow = true
|
||||
this.group.add(m)
|
||||
}
|
||||
|
||||
// site letters painted on the floor
|
||||
const markers: [string, number, number, string][] = [
|
||||
['A', 41, 5, 'rgba(214,120,50,0.85)'],
|
||||
['B', 7, 7, 'rgba(214,120,50,0.85)'],
|
||||
['MID', 26, 24, 'rgba(90,74,50,0.6)'],
|
||||
]
|
||||
for (const [text, c, r, color] of markers) {
|
||||
const tex = makeLetterTexture(text, color)
|
||||
const size = text.length > 1 ? 7 : 5
|
||||
const m = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(size, size),
|
||||
new THREE.MeshBasicMaterial({ map: tex, transparent: true, depthWrite: false }),
|
||||
)
|
||||
const p = this.cellCenter(c, r)
|
||||
m.rotation.x = -Math.PI / 2
|
||||
m.position.set(p.x, 0.02, p.z)
|
||||
this.group.add(m)
|
||||
}
|
||||
|
||||
// doors
|
||||
const midDoor = new DoorObj(this.cellCenter(26, 16).setX(((25 + 29) / 2 - HALF) * CELL), 4 * CELL)
|
||||
const longDoor = new DoorObj(this.cellCenter(45, 32).setX(((45 + 47) / 2 - HALF) * CELL), 2 * CELL)
|
||||
this.doors = [midDoor, longDoor]
|
||||
for (const d of this.doors) this.group.add(d.group)
|
||||
}
|
||||
|
||||
private buildMeta() {
|
||||
const cc = (c: number, r: number) => this.cellCenter(c, r)
|
||||
// spawns
|
||||
const tCells: [number, number][] = [[25, 46], [27, 46], [23, 45], [29, 45], [26, 47]]
|
||||
const ctCells: [number, number][] = [[27, 5], [29, 5], [25, 4], [31, 6], [28, 7]]
|
||||
this.spawns.T = tCells.map(([c, r]) => ({ pos: cc(c, r), yaw: 0 }))
|
||||
this.spawns.CT = ctCells.map(([c, r]) => ({ pos: cc(c, r), yaw: Math.PI }))
|
||||
|
||||
const an = this.anchors
|
||||
an.aSite = cc(41, 5); an.bSite = cc(7, 7)
|
||||
an.tSpawn = cc(26, 45); an.ctSpawn = cc(28, 6)
|
||||
an.midLow = cc(26, 32); an.midDoorS = cc(26, 18); an.midTop = cc(26, 11)
|
||||
an.catwalk = cc(33, 21); an.aShort = cc(36, 12)
|
||||
an.outsideLong = cc(38, 36); an.longMid = cc(45, 26); an.longTop = cc(45, 11)
|
||||
an.tunnelS = cc(18, 36); an.tunnelElbow = cc(8, 29); an.tunnelMid = cc(7, 20)
|
||||
an.bDoors = cc(18, 11)
|
||||
|
||||
this.routes.A = [
|
||||
[an.outsideLong, an.longMid, an.longTop],
|
||||
[an.midLow, an.catwalk, an.aShort],
|
||||
]
|
||||
this.routes.B = [
|
||||
[an.tunnelS, an.tunnelElbow, an.tunnelMid],
|
||||
[an.midLow, an.midDoorS, an.midTop, an.bDoors],
|
||||
]
|
||||
|
||||
this.holdSpots.A = [
|
||||
{ pos: cc(38, 3), look: cc(45, 11) },
|
||||
{ pos: cc(43, 8), look: cc(36, 13) },
|
||||
{ pos: cc(45, 11), look: cc(45, 28) },
|
||||
]
|
||||
this.holdSpots.B = [
|
||||
{ pos: cc(4, 4), look: cc(7, 15) },
|
||||
{ pos: cc(10, 10), look: cc(15, 11) },
|
||||
{ pos: cc(7, 14), look: cc(7, 22) },
|
||||
]
|
||||
this.holdSpots.mid = [
|
||||
{ pos: cc(26, 12), look: cc(26, 20) },
|
||||
{ pos: cc(18, 11), look: cc(25, 11) },
|
||||
]
|
||||
|
||||
for (let r = 0; r < GRID; r++) {
|
||||
for (let c = 0; c < GRID; c++) {
|
||||
const code = this.code(c, r)
|
||||
if (code === Code.ASite && this.isWalkableCell(c, r)) this.siteCells.A.push([c, r])
|
||||
if (code === Code.BSite && this.isWalkableCell(c, r)) this.siteCells.B.push([c, r])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
randomSiteSpot(site: 'A' | 'B'): THREE.Vector3 {
|
||||
const cells = this.siteCells[site]
|
||||
const [c, r] = cells[Math.floor(Math.random() * cells.length)]
|
||||
return this.cellCenter(c, r)
|
||||
}
|
||||
|
||||
/** Random walkable cell center within `radius` (world units) of a point. */
|
||||
randomSpotNear(p: THREE.Vector3, radius: number): THREE.Vector3 {
|
||||
const [pc, pr] = this.worldToCell(p.x, p.z)
|
||||
const cellRad = Math.ceil(radius / CELL)
|
||||
for (let i = 0; i < 24; i++) {
|
||||
const c = pc + Math.round((Math.random() * 2 - 1) * cellRad)
|
||||
const r = pr + Math.round((Math.random() * 2 - 1) * cellRad)
|
||||
if (this.isWalkableCell(c, r)) return this.cellCenter(c, r)
|
||||
}
|
||||
return p.clone()
|
||||
}
|
||||
|
||||
drawMinimap(ctx: CanvasRenderingContext2D, px: number) {
|
||||
ctx.clearRect(0, 0, GRID * px, GRID * px)
|
||||
for (let r = 0; r < GRID; r++) {
|
||||
for (let c = 0; c < GRID; c++) {
|
||||
const code = this.code(c, r)
|
||||
if (code === Code.Wall) continue
|
||||
let fill = 'rgba(219,197,150,0.85)'
|
||||
if (code === Code.ASite || code === Code.BSite) fill = 'rgba(226,160,96,0.9)'
|
||||
if (code === Code.TSpawn) fill = 'rgba(216,190,132,0.9)'
|
||||
if (code === Code.CTSpawn) fill = 'rgba(180,198,216,0.9)'
|
||||
if (code === Code.Door) fill = 'rgba(140,170,140,0.9)'
|
||||
ctx.fillStyle = fill
|
||||
ctx.fillRect(c * px, r * px, px, px)
|
||||
if (this.crateCells.has(r * GRID + c)) {
|
||||
ctx.fillStyle = 'rgba(120,95,55,0.9)'
|
||||
ctx.fillRect(c * px + 1, r * px + 1, px - 2, px - 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.font = `bold ${px * 5}px sans-serif`
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'middle'
|
||||
ctx.fillStyle = 'rgba(80,50,20,0.8)'
|
||||
ctx.fillText('A', 41.5 * px, 6 * px)
|
||||
ctx.fillText('B', 7.5 * px, 7.5 * px)
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import { CELL, GRID, GameMap } from './map'
|
||||
import type { PhysicsWorld } from './physics'
|
||||
|
||||
const HALF = GRID / 2
|
||||
|
||||
/** Grid A* with octile heuristic + string-pulling smoothing. */
|
||||
export class Nav {
|
||||
private map: GameMap
|
||||
private physics: PhysicsWorld
|
||||
private walkable: Uint8Array
|
||||
|
||||
constructor(map: GameMap, physics: PhysicsWorld) {
|
||||
this.map = map
|
||||
this.physics = physics
|
||||
this.walkable = new Uint8Array(GRID * GRID)
|
||||
for (let r = 0; r < GRID; r++) {
|
||||
for (let c = 0; c < GRID; c++) {
|
||||
this.walkable[r * GRID + c] = map.isWalkableCell(c, r) ? 1 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ok(c: number, r: number): boolean {
|
||||
return c >= 0 && r >= 0 && c < GRID && r < GRID && this.walkable[r * GRID + c] === 1
|
||||
}
|
||||
|
||||
private nearestWalkable(c: number, r: number): [number, number] | null {
|
||||
if (this.ok(c, r)) return [c, r]
|
||||
for (let rad = 1; rad <= 6; rad++) {
|
||||
for (let dr = -rad; dr <= rad; dr++) {
|
||||
for (let dc = -rad; dc <= rad; dc++) {
|
||||
if (Math.max(Math.abs(dc), Math.abs(dr)) !== rad) continue
|
||||
if (this.ok(c + dc, r + dr)) return [c + dc, r + dr]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
findPath(from: THREE.Vector3, to: THREE.Vector3): THREE.Vector3[] | null {
|
||||
const s = this.nearestWalkable(...this.map.worldToCell(from.x, from.z))
|
||||
const e = this.nearestWalkable(...this.map.worldToCell(to.x, to.z))
|
||||
if (!s || !e) return null
|
||||
const cells = this.aStar(s[0], s[1], e[0], e[1])
|
||||
if (!cells) return null
|
||||
|
||||
const pts: THREE.Vector3[] = cells.map(i => this.map.cellCenter(i % GRID, Math.floor(i / GRID)))
|
||||
pts.push(new THREE.Vector3(to.x, 0, to.z))
|
||||
|
||||
// string pulling
|
||||
const smoothed: THREE.Vector3[] = []
|
||||
let i = 0
|
||||
const start = new THREE.Vector3(from.x, 0, from.z)
|
||||
let cur = start
|
||||
while (i < pts.length) {
|
||||
let j = pts.length - 1
|
||||
let found = i
|
||||
for (; j > i; j--) {
|
||||
if (this.physics.walkClear(cur, pts[j], 0.45)) { found = j; break }
|
||||
}
|
||||
cur = pts[found]
|
||||
smoothed.push(cur)
|
||||
i = found + 1
|
||||
}
|
||||
return smoothed
|
||||
}
|
||||
|
||||
private aStar(sc: number, sr: number, ec: number, er: number): number[] | null {
|
||||
const n = GRID * GRID
|
||||
const g = new Float32Array(n).fill(Infinity)
|
||||
const came = new Int32Array(n).fill(-1)
|
||||
const closed = new Uint8Array(n)
|
||||
const start = sr * GRID + sc
|
||||
const end = er * GRID + ec
|
||||
g[start] = 0
|
||||
|
||||
// binary heap of [f, idx]
|
||||
const heapF: number[] = []
|
||||
const heapI: number[] = []
|
||||
const push = (f: number, idx: number) => {
|
||||
heapF.push(f); heapI.push(idx)
|
||||
let i = heapF.length - 1
|
||||
while (i > 0) {
|
||||
const p = (i - 1) >> 1
|
||||
if (heapF[p] <= heapF[i]) break
|
||||
;[heapF[p], heapF[i]] = [heapF[i], heapF[p]]
|
||||
;[heapI[p], heapI[i]] = [heapI[i], heapI[p]]
|
||||
i = p
|
||||
}
|
||||
}
|
||||
const pop = (): number => {
|
||||
const top = heapI[0]
|
||||
const lf = heapF.pop()!, li = heapI.pop()!
|
||||
if (heapF.length > 0) {
|
||||
heapF[0] = lf; heapI[0] = li
|
||||
let i = 0
|
||||
for (;;) {
|
||||
const l = i * 2 + 1, r = l + 1
|
||||
let m = i
|
||||
if (l < heapF.length && heapF[l] < heapF[m]) m = l
|
||||
if (r < heapF.length && heapF[r] < heapF[m]) m = r
|
||||
if (m === i) break
|
||||
;[heapF[m], heapF[i]] = [heapF[i], heapF[m]]
|
||||
;[heapI[m], heapI[i]] = [heapI[i], heapI[m]]
|
||||
i = m
|
||||
}
|
||||
}
|
||||
return top
|
||||
}
|
||||
const h = (idx: number) => {
|
||||
const dc = Math.abs((idx % GRID) - ec)
|
||||
const dr = Math.abs(Math.floor(idx / GRID) - er)
|
||||
return Math.max(dc, dr) + 0.414 * Math.min(dc, dr)
|
||||
}
|
||||
|
||||
push(h(start), start)
|
||||
let expansions = 0
|
||||
while (heapF.length > 0 && expansions < 8000) {
|
||||
const cur = pop()
|
||||
if (cur === end) {
|
||||
const path: number[] = []
|
||||
let c = end
|
||||
while (c !== -1) { path.push(c); c = came[c] }
|
||||
path.reverse()
|
||||
return path
|
||||
}
|
||||
if (closed[cur]) continue
|
||||
closed[cur] = 1
|
||||
expansions++
|
||||
const cc = cur % GRID, cr = Math.floor(cur / GRID)
|
||||
for (let dr = -1; dr <= 1; dr++) {
|
||||
for (let dc = -1; dc <= 1; dc++) {
|
||||
if (dc === 0 && dr === 0) continue
|
||||
const nc = cc + dc, nr = cr + dr
|
||||
if (!this.ok(nc, nr)) continue
|
||||
// no corner cutting
|
||||
if (dc !== 0 && dr !== 0 && (!this.ok(cc + dc, cr) || !this.ok(cc, cr + dr))) continue
|
||||
const ni = nr * GRID + nc
|
||||
if (closed[ni]) continue
|
||||
const cost = dc !== 0 && dr !== 0 ? 1.414 : 1
|
||||
const ng = g[cur] + cost
|
||||
if (ng < g[ni]) {
|
||||
g[ni] = ng
|
||||
came[ni] = cur
|
||||
push(ng + h(ni), ni)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export { CELL }
|
||||
@@ -1,258 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import { CELL, GRID, WALL_H, GameMap, type AABB } from './map'
|
||||
import type { BodyPart, SoldierBody } from './types'
|
||||
import { GRAVITY } from './types'
|
||||
|
||||
const HALF = GRID / 2
|
||||
const STEP = 0.35 // max ledge height walkable without jumping
|
||||
|
||||
export interface HitboxDef { part: BodyPart; min: [number, number, number]; max: [number, number, number] }
|
||||
|
||||
/** Local space, feet at origin, facing -Z. */
|
||||
export const HITBOXES: HitboxDef[] = [
|
||||
{ part: 'head', min: [-0.15, 1.48, -0.16], max: [0.15, 1.8, 0.16] },
|
||||
{ part: 'chest', min: [-0.24, 1.12, -0.15], max: [0.24, 1.48, 0.15] },
|
||||
{ part: 'stomach', min: [-0.22, 0.86, -0.14], max: [0.22, 1.12, 0.14] },
|
||||
{ part: 'arm', min: [-0.44, 1.0, -0.15], max: [-0.24, 1.48, 0.15] },
|
||||
{ part: 'arm', min: [0.24, 1.0, -0.15], max: [0.44, 1.48, 0.15] },
|
||||
{ part: 'leg', min: [-0.22, 0, -0.14], max: [-0.02, 0.9, 0.14] },
|
||||
{ part: 'leg', min: [0.02, 0, -0.14], max: [0.22, 0.9, 0.14] },
|
||||
]
|
||||
|
||||
export interface RayHit {
|
||||
t: number
|
||||
point: THREE.Vector3
|
||||
kind: 'wall' | 'crate' | 'door' | 'floor' | 'soldier'
|
||||
soldier?: SoldierBody
|
||||
part?: BodyPart
|
||||
}
|
||||
|
||||
export interface MovingBody {
|
||||
pos: THREE.Vector3 // feet
|
||||
vel: THREE.Vector3
|
||||
onGround: boolean
|
||||
radius: number
|
||||
height: number
|
||||
}
|
||||
|
||||
function rayAABB(ox: number, oy: number, oz: number, dx: number, dy: number, dz: number, box: AABB): number | null {
|
||||
let tmin = 0, tmax = Infinity
|
||||
const o = [ox, oy, oz], d = [dx, dy, dz]
|
||||
const mn = [box.min.x, box.min.y, box.min.z], mx = [box.max.x, box.max.y, box.max.z]
|
||||
for (let i = 0; i < 3; i++) {
|
||||
if (Math.abs(d[i]) < 1e-9) {
|
||||
if (o[i] < mn[i] || o[i] > mx[i]) return null
|
||||
} else {
|
||||
let t1 = (mn[i] - o[i]) / d[i]
|
||||
let t2 = (mx[i] - o[i]) / d[i]
|
||||
if (t1 > t2) { const tmp = t1; t1 = t2; t2 = tmp }
|
||||
if (t1 > tmin) tmin = t1
|
||||
if (t2 < tmax) tmax = t2
|
||||
if (tmin > tmax) return null
|
||||
}
|
||||
}
|
||||
return tmin
|
||||
}
|
||||
|
||||
export class PhysicsWorld {
|
||||
map: GameMap
|
||||
|
||||
constructor(map: GameMap) {
|
||||
this.map = map
|
||||
}
|
||||
|
||||
/** Is a circle at (x,z) with feet at y clear of walls/crates/doors? */
|
||||
circleFree(x: number, z: number, r: number, feetY: number, ignoreDoors = false): boolean {
|
||||
const cMin = Math.floor((x - r) / CELL + HALF), cMax = Math.floor((x + r) / CELL + HALF)
|
||||
const rMin = Math.floor((z - r) / CELL + HALF), rMax = Math.floor((z + r) / CELL + HALF)
|
||||
for (let cr = rMin; cr <= rMax; cr++) {
|
||||
for (let cc = cMin; cc <= cMax; cc++) {
|
||||
if (!this.map.isWall(cc, cr)) continue
|
||||
if (this.circleHitsRect(x, z, r, (cc - HALF) * CELL, (cr - HALF) * CELL, (cc - HALF + 1) * CELL, (cr - HALF + 1) * CELL)) return false
|
||||
}
|
||||
}
|
||||
for (const box of this.map.crates) {
|
||||
if (box.max.y <= feetY + STEP) continue // can step onto / stand on it
|
||||
if (this.circleHitsRect(x, z, r, box.min.x, box.min.z, box.max.x, box.max.z)) return false
|
||||
}
|
||||
if (!ignoreDoors) {
|
||||
for (const d of this.map.doors) {
|
||||
for (const box of d.colliders()) {
|
||||
if (this.circleHitsRect(x, z, r, box.min.x, box.min.z, box.max.x, box.max.z)) return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private circleHitsRect(x: number, z: number, r: number, x1: number, z1: number, x2: number, z2: number): boolean {
|
||||
const nx = Math.max(x1, Math.min(x, x2))
|
||||
const nz = Math.max(z1, Math.min(z, z2))
|
||||
const dx = x - nx, dz = z - nz
|
||||
return dx * dx + dz * dz < r * r
|
||||
}
|
||||
|
||||
/** Used by nav smoothing: walls + crates only (doors auto-open). */
|
||||
walkClear(a: THREE.Vector3, b: THREE.Vector3, radius: number): boolean {
|
||||
const dx = b.x - a.x, dz = b.z - a.z
|
||||
const dist = Math.hypot(dx, dz)
|
||||
const steps = Math.max(1, Math.ceil(dist / 0.35))
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const t = i / steps
|
||||
if (!this.circleFree(a.x + dx * t, a.z + dz * t, radius, 0, true)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
moveBody(body: MovingBody, wishWorld: THREE.Vector3, speed: number, jump: boolean, dt: number) {
|
||||
const accel = body.onGround ? 22 : 4
|
||||
const k = Math.min(1, accel * dt)
|
||||
body.vel.x += (wishWorld.x * speed - body.vel.x) * k
|
||||
body.vel.z += (wishWorld.z * speed - body.vel.z) * k
|
||||
|
||||
if (jump && body.onGround) {
|
||||
body.vel.y = 7.0
|
||||
body.onGround = false
|
||||
}
|
||||
|
||||
// axis-separated horizontal collision
|
||||
const nx = body.pos.x + body.vel.x * dt
|
||||
if (this.circleFree(nx, body.pos.z, body.radius, body.pos.y)) body.pos.x = nx
|
||||
else body.vel.x = 0
|
||||
const nz = body.pos.z + body.vel.z * dt
|
||||
if (this.circleFree(body.pos.x, nz, body.radius, body.pos.y)) body.pos.z = nz
|
||||
else body.vel.z = 0
|
||||
|
||||
// vertical
|
||||
const prevY = body.pos.y
|
||||
body.vel.y -= GRAVITY * dt
|
||||
body.pos.y += body.vel.y * dt
|
||||
const ground = this.groundHeight(body.pos.x, body.pos.z, body.radius, prevY)
|
||||
if (body.pos.y <= ground && body.vel.y <= 0) {
|
||||
body.pos.y = ground
|
||||
body.vel.y = 0
|
||||
body.onGround = true
|
||||
} else {
|
||||
body.onGround = false
|
||||
}
|
||||
}
|
||||
|
||||
groundHeight(x: number, z: number, r: number, feetY: number): number {
|
||||
let g = 0
|
||||
for (const box of this.map.crates) {
|
||||
if (box.max.y > feetY + STEP) continue
|
||||
if (this.circleHitsRect(x, z, r, box.min.x, box.min.z, box.max.x, box.max.z)) {
|
||||
if (box.max.y > g) g = box.max.y
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
raycast(
|
||||
origin: THREE.Vector3,
|
||||
dir: THREE.Vector3,
|
||||
maxDist: number,
|
||||
soldiers?: SoldierBody[],
|
||||
ignoreId?: number,
|
||||
ignoreTeam?: string,
|
||||
): RayHit | null {
|
||||
let best: RayHit | null = null
|
||||
const consider = (t: number, kind: RayHit['kind'], soldier?: SoldierBody, part?: BodyPart) => {
|
||||
if (t < 0 || t > maxDist) return
|
||||
if (best && t >= best.t) return
|
||||
best = {
|
||||
t, kind, soldier, part,
|
||||
point: new THREE.Vector3(origin.x + dir.x * t, origin.y + dir.y * t, origin.z + dir.z * t),
|
||||
}
|
||||
}
|
||||
|
||||
// floor
|
||||
if (dir.y < -1e-6) consider(-origin.y / dir.y, 'floor')
|
||||
|
||||
// walls via DDA
|
||||
const wt = this.wallRay(origin, dir, maxDist)
|
||||
if (wt !== null) consider(wt, 'wall')
|
||||
|
||||
// crates
|
||||
for (const box of this.map.crates) {
|
||||
const t = rayAABB(origin.x, origin.y, origin.z, dir.x, dir.y, dir.z, box)
|
||||
if (t !== null) consider(t, 'crate')
|
||||
}
|
||||
|
||||
// door panels
|
||||
for (const d of this.map.doors) {
|
||||
for (const box of d.colliders()) {
|
||||
const t = rayAABB(origin.x, origin.y, origin.z, dir.x, dir.y, dir.z, box)
|
||||
if (t !== null) consider(t, 'door')
|
||||
}
|
||||
}
|
||||
|
||||
// soldier hitboxes (ray transformed into each soldier's local yaw space)
|
||||
if (soldiers) {
|
||||
for (const s of soldiers) {
|
||||
if (!s.alive || s.id === ignoreId) continue
|
||||
if (ignoreTeam && s.team === ignoreTeam) continue
|
||||
const cos = Math.cos(-s.yaw), sin = Math.sin(-s.yaw)
|
||||
const rx = origin.x - s.pos.x, rz = origin.z - s.pos.z
|
||||
const lox = rx * cos - rz * sin
|
||||
const loz = rx * sin + rz * cos
|
||||
const loy = origin.y - s.pos.y
|
||||
const ldx = dir.x * cos - dir.z * sin
|
||||
const ldz = dir.x * sin + dir.z * cos
|
||||
for (const hb of HITBOXES) {
|
||||
const t = rayAABB(lox, loy, loz, ldx, dir.y, ldz, {
|
||||
min: new THREE.Vector3(hb.min[0], hb.min[1], hb.min[2]),
|
||||
max: new THREE.Vector3(hb.max[0], hb.max[1], hb.max[2]),
|
||||
})
|
||||
if (t !== null) consider(t, 'soldier', s, hb.part)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
private wallRay(origin: THREE.Vector3, dir: THREE.Vector3, maxDist: number): number | null {
|
||||
let cx = Math.floor(origin.x / CELL + HALF)
|
||||
let cz = Math.floor(origin.z / CELL + HALF)
|
||||
const stepX = dir.x > 0 ? 1 : -1
|
||||
const stepZ = dir.z > 0 ? 1 : -1
|
||||
const tDeltaX = Math.abs(dir.x) > 1e-9 ? CELL / Math.abs(dir.x) : Infinity
|
||||
const tDeltaZ = Math.abs(dir.z) > 1e-9 ? CELL / Math.abs(dir.z) : Infinity
|
||||
const nextGridX = (cx - HALF + (stepX > 0 ? 1 : 0)) * CELL
|
||||
const nextGridZ = (cz - HALF + (stepZ > 0 ? 1 : 0)) * CELL
|
||||
let tMaxX = Math.abs(dir.x) > 1e-9 ? (nextGridX - origin.x) / dir.x : Infinity
|
||||
let tMaxZ = Math.abs(dir.z) > 1e-9 ? (nextGridZ - origin.z) / dir.z : Infinity
|
||||
|
||||
if (this.map.isWall(cx, cz)) return 0
|
||||
|
||||
let t = 0
|
||||
for (let i = 0; i < GRID * 3; i++) {
|
||||
if (tMaxX < tMaxZ) {
|
||||
t = tMaxX
|
||||
tMaxX += tDeltaX
|
||||
cx += stepX
|
||||
} else {
|
||||
t = tMaxZ
|
||||
tMaxZ += tDeltaZ
|
||||
cz += stepZ
|
||||
}
|
||||
if (t > maxDist) return null
|
||||
if (this.map.isWall(cx, cz)) {
|
||||
const y = origin.y + dir.y * t
|
||||
if (y >= 0 && y <= WALL_H) return t
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** Line of sight between two points against static world only. */
|
||||
hasLOS(a: THREE.Vector3, b: THREE.Vector3): boolean {
|
||||
const dir = new THREE.Vector3().subVectors(b, a)
|
||||
const dist = dir.length()
|
||||
if (dist < 1e-4) return true
|
||||
dir.multiplyScalar(1 / dist)
|
||||
const hit = this.raycast(a, dir, dist * 0.99)
|
||||
return hit === null
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import { CharacterRig } from './character'
|
||||
import { WEAPONS, WeaponState } from './weapons'
|
||||
import {
|
||||
BODY_HEIGHT, BODY_RADIUS, EYE_HEIGHT,
|
||||
emptyControl, type Control, type Slot, type Team, type WeaponId,
|
||||
} from './types'
|
||||
import type { AIBrain } from './ai'
|
||||
|
||||
export class Soldier {
|
||||
id: number
|
||||
name: string
|
||||
originalName: string
|
||||
team: Team
|
||||
isPlayer = false
|
||||
|
||||
pos = new THREE.Vector3()
|
||||
vel = new THREE.Vector3()
|
||||
yaw = 0
|
||||
pitch = 0
|
||||
onGround = true
|
||||
radius = BODY_RADIUS
|
||||
height = BODY_HEIGHT
|
||||
|
||||
hp = 100
|
||||
armor = 0
|
||||
alive = true
|
||||
kills = 0
|
||||
deaths = 0
|
||||
|
||||
weapons: Partial<Record<Slot, WeaponState>> = {}
|
||||
slot: Slot = 'secondary'
|
||||
scoped = false
|
||||
switchCooldown = 0
|
||||
|
||||
hasBomb = false
|
||||
channel: { kind: 'plant' | 'defuse'; t: number; total: number } | null = null
|
||||
|
||||
control: Control = emptyControl()
|
||||
brain: AIBrain | null = null
|
||||
rig: CharacterRig
|
||||
|
||||
recoilPitch = 0
|
||||
recoilYaw = 0
|
||||
stepAcc = 0
|
||||
/** buffered semi-auto trigger: a click stays valid for a short window */
|
||||
tapBuffer = 0
|
||||
lastSeenBy: Record<Team, number> = { CT: -99, T: -99 }
|
||||
wasReloading = false
|
||||
|
||||
constructor(id: number, name: string, team: Team) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.originalName = name
|
||||
this.team = team
|
||||
this.rig = new CharacterRig(team)
|
||||
}
|
||||
|
||||
get weapon(): WeaponState {
|
||||
return this.weapons[this.slot] ?? this.weapons.knife!
|
||||
}
|
||||
|
||||
get eyePos(): THREE.Vector3 {
|
||||
return new THREE.Vector3(this.pos.x, this.pos.y + EYE_HEIGHT, this.pos.z)
|
||||
}
|
||||
|
||||
/** View direction including recoil offset (recoil affects both camera and bullets). */
|
||||
viewDir(): THREE.Vector3 {
|
||||
const yaw = this.yaw + this.recoilYaw
|
||||
const pitch = THREE.MathUtils.clamp(this.pitch + this.recoilPitch, -1.55, 1.55)
|
||||
const cp = Math.cos(pitch)
|
||||
return new THREE.Vector3(-Math.sin(yaw) * cp, Math.sin(pitch), -Math.cos(yaw) * cp)
|
||||
}
|
||||
|
||||
rightDir(): THREE.Vector3 {
|
||||
return new THREE.Vector3(Math.cos(this.yaw), 0, -Math.sin(this.yaw))
|
||||
}
|
||||
|
||||
equip(primary: WeaponId | null, secondary: WeaponId, armor: number) {
|
||||
this.weapons = { knife: new WeaponState(WEAPONS.knife) }
|
||||
this.weapons.secondary = new WeaponState(WEAPONS[secondary])
|
||||
if (primary) this.weapons.primary = new WeaponState(WEAPONS[primary])
|
||||
this.slot = primary ? 'primary' : 'secondary'
|
||||
this.armor = armor
|
||||
this.scoped = false
|
||||
this.rig.setWeapon(this.weapon.def.id)
|
||||
}
|
||||
|
||||
respawn(pos: THREE.Vector3, yaw: number) {
|
||||
this.pos.copy(pos)
|
||||
this.vel.set(0, 0, 0)
|
||||
this.yaw = yaw
|
||||
this.pitch = 0
|
||||
this.hp = 100
|
||||
this.alive = true
|
||||
this.onGround = true
|
||||
this.channel = null
|
||||
this.hasBomb = false
|
||||
this.recoilPitch = 0
|
||||
this.recoilYaw = 0
|
||||
this.scoped = false
|
||||
this.control = emptyControl()
|
||||
this.rig.spawnReset()
|
||||
this.rig.setBomb(false)
|
||||
}
|
||||
|
||||
trySwitch(slot: Slot): boolean {
|
||||
if (!this.alive || slot === this.slot || !this.weapons[slot]) return false
|
||||
this.slot = slot
|
||||
this.scoped = false
|
||||
this.switchCooldown = 0.4
|
||||
this.weapon.cooldown = Math.max(this.weapon.cooldown, 0.4)
|
||||
this.rig.setWeapon(this.weapon.def.id)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { HudState } from './types'
|
||||
|
||||
export function initialHud(): HudState {
|
||||
return {
|
||||
phase: 'menu',
|
||||
locked: false,
|
||||
auto: false,
|
||||
round: 1,
|
||||
pistolRound: true,
|
||||
ctScore: 0,
|
||||
tScore: 0,
|
||||
timeLeft: 0,
|
||||
bombTimeLeft: null,
|
||||
bombPlanted: false,
|
||||
freezeLeft: 0,
|
||||
winner: null,
|
||||
winReason: '',
|
||||
matchWinner: null,
|
||||
playerTeam: 'CT',
|
||||
loadout: 'rifle',
|
||||
me: {
|
||||
name: '你', team: 'CT', hp: 100, armor: 0, alive: true,
|
||||
weaponName: '', weaponId: 'knife', mag: 0, reserve: 0,
|
||||
reloading: false, scoped: false, hasBomb: false, spreadPx: 4,
|
||||
},
|
||||
spectating: null,
|
||||
channel: null,
|
||||
centerHint: null,
|
||||
killfeed: [],
|
||||
hitmarkerAt: -99,
|
||||
hitmarkerHead: false,
|
||||
damageAt: -99,
|
||||
}
|
||||
}
|
||||
|
||||
/** Tiny external store bridging the game loop and React (useSyncExternalStore). */
|
||||
export class HudStore {
|
||||
state: HudState = initialHud()
|
||||
private version = 0
|
||||
private listeners = new Set<() => void>()
|
||||
|
||||
subscribe = (fn: () => void) => {
|
||||
this.listeners.add(fn)
|
||||
return () => { this.listeners.delete(fn) }
|
||||
}
|
||||
|
||||
getSnapshot = () => this.version
|
||||
|
||||
bump() {
|
||||
this.version++
|
||||
for (const fn of this.listeners) fn()
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
|
||||
export type Team = 'CT' | 'T'
|
||||
export type Slot = 'primary' | 'secondary' | 'knife'
|
||||
export type BodyPart = 'head' | 'chest' | 'stomach' | 'arm' | 'leg'
|
||||
export type WeaponId = 'ak47' | 'm4a4' | 'awp' | 'glock' | 'usp' | 'deagle' | 'knife'
|
||||
export type Phase = 'menu' | 'freeze' | 'live' | 'over' | 'matchover'
|
||||
export type Loadout = 'rifle' | 'awp'
|
||||
|
||||
/** Per-tick intent, produced by player input or an AI brain, consumed by physics/combat. */
|
||||
export interface Control {
|
||||
moveX: number // strafe, +right
|
||||
moveZ: number // +forward
|
||||
jump: boolean
|
||||
walk: boolean
|
||||
fire: boolean // held
|
||||
fireTap: boolean // edge-triggered (semi-auto)
|
||||
altTap: boolean // scope toggle
|
||||
reload: boolean
|
||||
use: boolean // E held: plant / defuse
|
||||
slot: Slot | null
|
||||
}
|
||||
|
||||
export function emptyControl(): Control {
|
||||
return {
|
||||
moveX: 0, moveZ: 0, jump: false, walk: false,
|
||||
fire: false, fireTap: false, altTap: false,
|
||||
reload: false, use: false, slot: null,
|
||||
}
|
||||
}
|
||||
|
||||
export interface KillEntry {
|
||||
id: number
|
||||
attacker: string
|
||||
attackerTeam: Team
|
||||
victim: string
|
||||
victimTeam: Team
|
||||
weapon: string
|
||||
headshot: boolean
|
||||
t: number
|
||||
}
|
||||
|
||||
export interface HudState {
|
||||
phase: Phase
|
||||
locked: boolean
|
||||
auto: boolean
|
||||
round: number
|
||||
pistolRound: boolean
|
||||
ctScore: number
|
||||
tScore: number
|
||||
timeLeft: number
|
||||
bombTimeLeft: number | null
|
||||
bombPlanted: boolean
|
||||
freezeLeft: number
|
||||
winner: Team | null
|
||||
winReason: string
|
||||
matchWinner: Team | null
|
||||
playerTeam: Team
|
||||
loadout: Loadout
|
||||
me: {
|
||||
name: string
|
||||
team: Team
|
||||
hp: number
|
||||
armor: number
|
||||
alive: boolean
|
||||
weaponName: string
|
||||
weaponId: WeaponId
|
||||
mag: number
|
||||
reserve: number
|
||||
reloading: boolean
|
||||
scoped: boolean
|
||||
hasBomb: boolean
|
||||
spreadPx: number
|
||||
}
|
||||
spectating: { name: string; isSelf: boolean } | null
|
||||
channel: { label: string; progress: number } | null
|
||||
centerHint: string | null
|
||||
killfeed: KillEntry[]
|
||||
hitmarkerAt: number
|
||||
hitmarkerHead: boolean
|
||||
damageAt: number
|
||||
}
|
||||
|
||||
/** Minimal body info physics needs for hitbox raycasts (avoids circular imports). */
|
||||
export interface SoldierBody {
|
||||
id: number
|
||||
team: Team
|
||||
alive: boolean
|
||||
pos: THREE.Vector3
|
||||
yaw: number
|
||||
}
|
||||
|
||||
export const RUN_SPEED = 4.7
|
||||
export const GRAVITY = 20
|
||||
export const JUMP_VEL = 7.0
|
||||
export const EYE_HEIGHT = 1.62
|
||||
export const BODY_RADIUS = 0.4
|
||||
export const BODY_HEIGHT = 1.8
|
||||
|
||||
export const PLANT_TIME = 3.2
|
||||
export const DEFUSE_TIME = 7.0
|
||||
export const BOMB_FUSE = 40
|
||||
export const ROUND_TIME = 115
|
||||
export const FREEZE_TIME = 4
|
||||
export const ROUND_END_TIME = 5
|
||||
export const WIN_SCORE = 13
|
||||
@@ -1,202 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import type { WeaponId } from './types'
|
||||
|
||||
function mat(color: number, metalness = 0.2): THREE.MeshStandardMaterial {
|
||||
return new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness })
|
||||
}
|
||||
|
||||
function box(w: number, h: number, d: number, m: THREE.Material): THREE.Mesh {
|
||||
return new THREE.Mesh(new THREE.BoxGeometry(w, h, d), m)
|
||||
}
|
||||
|
||||
/** First-person weapon models, attached to the camera. Procedural geometry only. */
|
||||
export class ViewModel {
|
||||
group = new THREE.Group()
|
||||
private models = new Map<WeaponId, THREE.Group>()
|
||||
private current: WeaponId = 'knife'
|
||||
private kick = 0
|
||||
private raise = 1
|
||||
private slashT = 0
|
||||
private reloadT = 0
|
||||
private reloadDur = 0
|
||||
private bobPhase = 0
|
||||
private flash: THREE.Mesh
|
||||
private flashT = 0
|
||||
|
||||
constructor(camera: THREE.Camera) {
|
||||
this.group.position.set(0.26, -0.24, -0.5)
|
||||
this.group.scale.setScalar(0.82)
|
||||
camera.add(this.group)
|
||||
|
||||
const skin = mat(0xd8b094, 0)
|
||||
for (const id of ['ak47', 'm4a4', 'awp', 'glock', 'usp', 'deagle', 'knife'] as WeaponId[]) {
|
||||
const g = this.build(id)
|
||||
// hands
|
||||
const handL = box(0.05, 0.05, 0.07, skin)
|
||||
const handR = box(0.05, 0.05, 0.07, skin)
|
||||
if (id === 'knife') {
|
||||
handR.position.set(0, -0.03, 0.1)
|
||||
g.add(handR)
|
||||
} else {
|
||||
handL.position.set(-0.01, -0.04, -0.22)
|
||||
handR.position.set(0.01, -0.05, 0.05)
|
||||
g.add(handL, handR)
|
||||
}
|
||||
g.visible = false
|
||||
this.group.add(g)
|
||||
this.models.set(id, g)
|
||||
}
|
||||
|
||||
this.flash = new THREE.Mesh(
|
||||
new THREE.PlaneGeometry(0.16, 0.16),
|
||||
new THREE.MeshBasicMaterial({ color: 0xffd080, transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false }),
|
||||
)
|
||||
this.flash.position.set(0, 0.02, -0.85)
|
||||
this.group.add(this.flash)
|
||||
this.setWeapon('knife', false)
|
||||
}
|
||||
|
||||
private build(id: WeaponId): THREE.Group {
|
||||
const g = new THREE.Group()
|
||||
const dark = mat(0x35353a)
|
||||
const darker = mat(0x232326)
|
||||
switch (id) {
|
||||
case 'ak47': {
|
||||
const wood = mat(0x6f4c2e, 0)
|
||||
const recv = box(0.055, 0.09, 0.42, dark); recv.position.z = -0.1
|
||||
const guard = box(0.055, 0.07, 0.2, wood); guard.position.set(0, -0.005, -0.38)
|
||||
const barrel = box(0.025, 0.025, 0.3, darker); barrel.position.z = -0.6
|
||||
const sight = box(0.012, 0.05, 0.012, darker); sight.position.set(0, 0.06, -0.68)
|
||||
const magM = box(0.04, 0.18, 0.1, mat(0x8a6a3a)); magM.position.set(0, -0.13, -0.02); magM.rotation.x = 0.45
|
||||
const stock = box(0.045, 0.08, 0.2, wood); stock.position.set(0, -0.01, 0.2)
|
||||
g.add(recv, guard, barrel, sight, magM, stock)
|
||||
break
|
||||
}
|
||||
case 'm4a4': {
|
||||
const recv = box(0.055, 0.09, 0.44, dark); recv.position.z = -0.12
|
||||
const rail = box(0.03, 0.03, 0.3, darker); rail.position.set(0, 0.065, -0.2)
|
||||
const barrel = box(0.024, 0.024, 0.32, darker); barrel.position.z = -0.62
|
||||
const magM = box(0.04, 0.16, 0.08, darker); magM.position.set(0, -0.12, -0.06)
|
||||
const stock = box(0.05, 0.075, 0.18, dark); stock.position.set(0, 0, 0.2)
|
||||
const grip = box(0.03, 0.09, 0.045, darker); grip.position.set(0, -0.09, 0.08); grip.rotation.x = 0.3
|
||||
g.add(recv, rail, barrel, magM, stock, grip)
|
||||
break
|
||||
}
|
||||
case 'awp': {
|
||||
const olive = mat(0x4c5f3b, 0)
|
||||
const body = box(0.055, 0.095, 0.6, olive); body.position.z = -0.15
|
||||
const barrel = box(0.022, 0.022, 0.5, darker); barrel.position.z = -0.78
|
||||
const muzzleBrake = box(0.035, 0.035, 0.06, darker); muzzleBrake.position.z = -1.0
|
||||
const scope = new THREE.Mesh(new THREE.CylinderGeometry(0.032, 0.032, 0.26, 10), darker)
|
||||
scope.rotation.x = Math.PI / 2
|
||||
scope.position.set(0, 0.085, -0.15)
|
||||
const stock = box(0.05, 0.1, 0.22, olive); stock.position.set(0, -0.01, 0.2)
|
||||
const bolt = box(0.015, 0.015, 0.08, mat(0xa8a8b0, 0.7)); bolt.position.set(0.045, 0.03, 0.02)
|
||||
g.add(body, barrel, muzzleBrake, scope, stock, bolt)
|
||||
break
|
||||
}
|
||||
case 'glock': {
|
||||
const slide = box(0.04, 0.055, 0.22, dark); slide.position.z = -0.05
|
||||
const frame = box(0.038, 0.05, 0.18, darker); frame.position.set(0, -0.045, -0.02)
|
||||
const grip = box(0.036, 0.12, 0.055, darker); grip.position.set(0, -0.1, 0.05); grip.rotation.x = 0.25
|
||||
g.add(slide, frame, grip)
|
||||
break
|
||||
}
|
||||
case 'usp': {
|
||||
const slide = box(0.04, 0.055, 0.26, mat(0x4a4a52, 0.5)); slide.position.z = -0.07
|
||||
const supp = box(0.045, 0.045, 0.14, darker); supp.position.z = -0.27
|
||||
const grip = box(0.038, 0.12, 0.055, darker); grip.position.set(0, -0.1, 0.05); grip.rotation.x = 0.22
|
||||
g.add(slide, supp, grip)
|
||||
break
|
||||
}
|
||||
case 'deagle': {
|
||||
const silver = mat(0xb4b4bc, 0.8)
|
||||
const slide = box(0.05, 0.07, 0.3, silver); slide.position.z = -0.08
|
||||
const grip = box(0.04, 0.13, 0.06, darker); grip.position.set(0, -0.1, 0.06); grip.rotation.x = 0.25
|
||||
g.add(slide, grip)
|
||||
break
|
||||
}
|
||||
case 'knife': {
|
||||
const blade = box(0.012, 0.04, 0.26, mat(0xd6dde4, 0.9)); blade.position.z = -0.16
|
||||
const edge = box(0.006, 0.012, 0.24, mat(0xf2f6fa, 1)); edge.position.set(0, -0.026, -0.15)
|
||||
const guardK = box(0.04, 0.015, 0.02, darker); guardK.position.z = -0.02
|
||||
const handle = box(0.028, 0.045, 0.13, mat(0x2c2c30, 0)); handle.position.z = 0.06
|
||||
g.add(blade, edge, guardK, handle)
|
||||
g.rotation.z = 0.25
|
||||
g.rotation.y = 0.28
|
||||
break
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
setWeapon(id: WeaponId, animate = true) {
|
||||
if (id === this.current && animate) return
|
||||
this.current = id
|
||||
for (const [k, g] of this.models) g.visible = k === id
|
||||
if (animate) this.raise = 0
|
||||
this.reloadT = 0
|
||||
this.reloadDur = 0
|
||||
}
|
||||
|
||||
onFire(kickScale: number) {
|
||||
if (this.current === 'knife') {
|
||||
this.slashT = 0.3
|
||||
return
|
||||
}
|
||||
this.kick = Math.min(1.6, this.kick + 0.5 + kickScale * 18)
|
||||
this.flashT = 0.05
|
||||
if (this.current !== 'awp') {
|
||||
;(this.flash.material as THREE.MeshBasicMaterial).opacity = 0.9
|
||||
}
|
||||
}
|
||||
|
||||
onReload(duration: number) {
|
||||
this.reloadDur = duration
|
||||
this.reloadT = 0.0001
|
||||
}
|
||||
|
||||
update(dt: number, speed: number, onGround: boolean, scoped: boolean) {
|
||||
this.kick *= Math.exp(-13 * dt)
|
||||
this.raise = Math.min(1, this.raise + dt / 0.28)
|
||||
this.flashT -= dt
|
||||
if (this.flashT <= 0) (this.flash.material as THREE.MeshBasicMaterial).opacity = 0
|
||||
if (this.reloadDur > 0) {
|
||||
this.reloadT += dt
|
||||
if (this.reloadT >= this.reloadDur) { this.reloadT = 0; this.reloadDur = 0 }
|
||||
}
|
||||
|
||||
this.group.visible = !scoped
|
||||
const speedRatio = onGround ? Math.min(1, speed / 4.7) : 0.2
|
||||
this.bobPhase += speed * dt * 1.9
|
||||
|
||||
let px = 0.26, py = -0.24, pz = -0.5
|
||||
let rx = 0, ry = 0, rz = 0
|
||||
py += Math.abs(Math.sin(this.bobPhase)) * 0.012 * speedRatio - 0.006 * speedRatio
|
||||
px += Math.sin(this.bobPhase * 0.5) * 0.008 * speedRatio
|
||||
pz += this.kick * 0.05
|
||||
rx += this.kick * 0.06
|
||||
py -= (1 - this.raise) * 0.3
|
||||
rx -= (1 - this.raise) * 0.6
|
||||
if (this.reloadDur > 0) {
|
||||
const p = Math.sin(Math.PI * Math.min(1, this.reloadT / this.reloadDur))
|
||||
py -= p * 0.09
|
||||
rz += p * 0.5
|
||||
rx -= p * 0.25
|
||||
}
|
||||
if (this.slashT > 0) {
|
||||
this.slashT = Math.max(0, this.slashT - dt)
|
||||
const p = 1 - this.slashT / 0.3 // 0 → 1 over the swing
|
||||
const sweep = Math.sin(p * Math.PI)
|
||||
// diagonal slash: wind up right, cut across to lower-left
|
||||
px += 0.1 - p * 0.32
|
||||
py += 0.06 - p * 0.2
|
||||
pz -= sweep * 0.22
|
||||
ry += 0.5 - p * 1.1
|
||||
rz -= sweep * 0.9
|
||||
rx -= sweep * 0.35
|
||||
}
|
||||
this.group.position.set(px, py, pz)
|
||||
this.group.rotation.set(rx, ry, rz)
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import type { BodyPart, Slot, WeaponId } from './types'
|
||||
|
||||
export interface WeaponDef {
|
||||
id: WeaponId
|
||||
name: string
|
||||
slot: Slot
|
||||
damage: number
|
||||
auto: boolean
|
||||
rpm: number
|
||||
magSize: number
|
||||
reserve: number
|
||||
reloadTime: number
|
||||
/** standing-still base inaccuracy, radians */
|
||||
spreadBase: number
|
||||
/** extra inaccuracy per shot fired (bloom) */
|
||||
spreadPerShot: number
|
||||
/** exponential bloom decay rate, 1/s */
|
||||
spreadDecay: number
|
||||
/** extra spread at full run speed */
|
||||
spreadMove: number
|
||||
/** camera kick per shot, radians */
|
||||
kickPitch: number
|
||||
kickYaw: number
|
||||
moveMult: number
|
||||
/** damage kept when target has armor (higher = better penetration) */
|
||||
armorPen: number
|
||||
sound: 'ak' | 'm4' | 'awp' | 'pistol' | 'glock' | 'deagle' | 'knife'
|
||||
scopeFov?: number
|
||||
spreadScoped?: number
|
||||
range?: number
|
||||
}
|
||||
|
||||
export const WEAPONS: Record<WeaponId, WeaponDef> = {
|
||||
ak47: {
|
||||
id: 'ak47', name: 'AK-47', slot: 'primary',
|
||||
damage: 36, auto: true, rpm: 600, magSize: 30, reserve: 90, reloadTime: 2.4,
|
||||
spreadBase: 0.006, spreadPerShot: 0.011, spreadDecay: 5.5, spreadMove: 0.045,
|
||||
kickPitch: 0.019, kickYaw: 0.006, moveMult: 0.92, armorPen: 0.775, sound: 'ak',
|
||||
},
|
||||
m4a4: {
|
||||
id: 'm4a4', name: 'M4A4', slot: 'primary',
|
||||
damage: 33, auto: true, rpm: 666, magSize: 30, reserve: 90, reloadTime: 2.1,
|
||||
spreadBase: 0.005, spreadPerShot: 0.007, spreadDecay: 6.5, spreadMove: 0.04,
|
||||
kickPitch: 0.009, kickYaw: 0.004, moveMult: 0.94, armorPen: 0.7, sound: 'm4',
|
||||
},
|
||||
awp: {
|
||||
id: 'awp', name: 'AWP', slot: 'primary',
|
||||
damage: 115, auto: false, rpm: 41, magSize: 5, reserve: 30, reloadTime: 3.6,
|
||||
spreadBase: 0.07, spreadPerShot: 0.01, spreadDecay: 3, spreadMove: 0.09,
|
||||
kickPitch: 0.032, kickYaw: 0.004, moveMult: 0.84, armorPen: 0.975, sound: 'awp',
|
||||
scopeFov: 20, spreadScoped: 0.0012,
|
||||
},
|
||||
glock: {
|
||||
id: 'glock', name: 'Glock-18', slot: 'secondary',
|
||||
damage: 26, auto: false, rpm: 400, magSize: 20, reserve: 120, reloadTime: 2.2,
|
||||
spreadBase: 0.009, spreadPerShot: 0.012, spreadDecay: 7, spreadMove: 0.02,
|
||||
kickPitch: 0.008, kickYaw: 0.003, moveMult: 1.0, armorPen: 0.47, sound: 'glock',
|
||||
},
|
||||
usp: {
|
||||
id: 'usp', name: 'USP-S', slot: 'secondary',
|
||||
damage: 33, auto: false, rpm: 352, magSize: 12, reserve: 24, reloadTime: 2.2,
|
||||
spreadBase: 0.007, spreadPerShot: 0.011, spreadDecay: 7, spreadMove: 0.02,
|
||||
kickPitch: 0.009, kickYaw: 0.003, moveMult: 1.0, armorPen: 0.505, sound: 'pistol',
|
||||
},
|
||||
deagle: {
|
||||
id: 'deagle', name: 'Desert Eagle', slot: 'secondary',
|
||||
damage: 53, auto: false, rpm: 267, magSize: 7, reserve: 35, reloadTime: 2.2,
|
||||
spreadBase: 0.012, spreadPerShot: 0.042, spreadDecay: 4.5, spreadMove: 0.05,
|
||||
kickPitch: 0.028, kickYaw: 0.008, moveMult: 0.96, armorPen: 0.8, sound: 'deagle',
|
||||
},
|
||||
knife: {
|
||||
id: 'knife', name: '军刀', slot: 'knife',
|
||||
damage: 45, auto: true, rpm: 120, magSize: 0, reserve: 0, reloadTime: 0,
|
||||
spreadBase: 0, spreadPerShot: 0, spreadDecay: 1, spreadMove: 0,
|
||||
kickPitch: 0.004, kickYaw: 0, moveMult: 1.05, armorPen: 0.85, sound: 'knife',
|
||||
range: 2.2,
|
||||
},
|
||||
}
|
||||
|
||||
/** 头部伤害为身体(胸)的两倍,其余部位各不相同。 */
|
||||
export const PART_MULT: Record<BodyPart, number> = {
|
||||
head: 2.0,
|
||||
chest: 1.0,
|
||||
stomach: 1.25,
|
||||
arm: 0.85,
|
||||
leg: 0.7,
|
||||
}
|
||||
|
||||
export class WeaponState {
|
||||
def: WeaponDef
|
||||
mag: number
|
||||
reserve: number
|
||||
cooldown = 0
|
||||
bloom = 0
|
||||
reloadLeft = 0
|
||||
|
||||
constructor(def: WeaponDef) {
|
||||
this.def = def
|
||||
this.mag = def.magSize
|
||||
this.reserve = def.reserve
|
||||
}
|
||||
|
||||
get reloading() { return this.reloadLeft > 0 }
|
||||
get isKnife() { return this.def.id === 'knife' }
|
||||
|
||||
/** Returns true the tick a reload completes. */
|
||||
update(dt: number): boolean {
|
||||
if (this.cooldown > 0) this.cooldown -= dt
|
||||
this.bloom *= Math.exp(-this.def.spreadDecay * dt)
|
||||
if (this.reloadLeft > 0) {
|
||||
this.reloadLeft -= dt
|
||||
if (this.reloadLeft <= 0) {
|
||||
this.reloadLeft = 0
|
||||
const take = Math.min(this.def.magSize - this.mag, this.reserve)
|
||||
this.mag += take
|
||||
this.reserve -= take
|
||||
this.bloom = 0
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
canFire(): boolean {
|
||||
if (this.reloading || this.cooldown > 0) return false
|
||||
return this.isKnife || this.mag > 0
|
||||
}
|
||||
|
||||
onFire() {
|
||||
if (!this.isKnife) this.mag--
|
||||
this.cooldown = 60 / this.def.rpm
|
||||
this.bloom = Math.min(this.bloom + this.def.spreadPerShot, 0.08)
|
||||
}
|
||||
|
||||
startReload(): boolean {
|
||||
if (this.isKnife || this.reloading) return false
|
||||
if (this.mag >= this.def.magSize || this.reserve <= 0) return false
|
||||
this.reloadLeft = this.def.reloadTime
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './ui/hud.css'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />)
|
||||
@@ -1,290 +0,0 @@
|
||||
import { useEffect, useRef, useSyncExternalStore } from 'react'
|
||||
import type { Engine } from '../game/engine'
|
||||
import { GRID } from '../game/map'
|
||||
import type { HudState, Team } from '../game/types'
|
||||
|
||||
const MINIMAP_PX = 4 // px per cell
|
||||
|
||||
function fmtTime(t: number): string {
|
||||
const s = Math.max(0, Math.ceil(t))
|
||||
return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function Minimap({ engine }: { engine: Engine }) {
|
||||
const bgRef = useRef<HTMLCanvasElement>(null)
|
||||
const fgRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const bg = bgRef.current!
|
||||
engine.map.drawMinimap(bg.getContext('2d')!, MINIMAP_PX)
|
||||
|
||||
let raf = 0
|
||||
const draw = () => {
|
||||
raf = requestAnimationFrame(draw)
|
||||
const fg = fgRef.current
|
||||
if (!fg) return
|
||||
const ctx = fg.getContext('2d')!
|
||||
const size = GRID * MINIMAP_PX
|
||||
ctx.clearRect(0, 0, size, size)
|
||||
const info = engine.minimapInfo()
|
||||
const toPx = (x: number, z: number): [number, number] => [
|
||||
(x / 2.5 + GRID / 2) * MINIMAP_PX,
|
||||
(z / 2.5 + GRID / 2) * MINIMAP_PX,
|
||||
]
|
||||
|
||||
// bomb
|
||||
const bomb = info.bomb
|
||||
let bombPos = bomb.plantedPos ?? bomb.droppedPos
|
||||
if (!bombPos && bomb.carrierId !== null) {
|
||||
const carrier = info.soldiers[bomb.carrierId]
|
||||
if (carrier && (carrier.team === info.playerTeam || carrier.lastSeenBy[info.playerTeam] > info.time - 2)) {
|
||||
bombPos = carrier.pos
|
||||
}
|
||||
}
|
||||
if (bombPos && (bomb.plantedPos ? Math.sin(info.time * 10) > -0.4 : true)) {
|
||||
const [bx, bz] = toPx(bombPos.x, bombPos.z)
|
||||
ctx.fillStyle = '#ff7828'
|
||||
ctx.fillRect(bx - 4, bz - 4, 8, 8)
|
||||
ctx.fillStyle = '#000'
|
||||
ctx.font = 'bold 7px sans-serif'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText('C4', bx, bz + 2.5)
|
||||
}
|
||||
|
||||
for (const s of info.soldiers) {
|
||||
if (!s.alive) continue
|
||||
const mine = s.team === info.playerTeam
|
||||
const seen = s.lastSeenBy[info.playerTeam] > info.time - 1.6
|
||||
if (!mine && !seen) continue
|
||||
const [px, pz] = toPx(s.pos.x, s.pos.z)
|
||||
if (s.id === info.viewedId) {
|
||||
// white arrow with view direction
|
||||
ctx.save()
|
||||
ctx.translate(px, pz)
|
||||
ctx.rotate(-s.yaw)
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, -6)
|
||||
ctx.lineTo(4.4, 4.4)
|
||||
ctx.lineTo(0, 2)
|
||||
ctx.lineTo(-4.4, 4.4)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
} else {
|
||||
ctx.fillStyle = mine ? '#57c46a' : '#e84c3d'
|
||||
ctx.beginPath()
|
||||
ctx.arc(px, pz, 3.4, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
raf = requestAnimationFrame(draw)
|
||||
return () => cancelAnimationFrame(raf)
|
||||
}, [engine])
|
||||
|
||||
const size = GRID * MINIMAP_PX
|
||||
return (
|
||||
<div className="minimap">
|
||||
<canvas ref={bgRef} width={size} height={size} />
|
||||
<canvas ref={fgRef} width={size} height={size} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Crosshair({ st }: { st: HudState }) {
|
||||
if (st.me.scoped) return null
|
||||
if (!st.me.alive) return null
|
||||
if (st.me.weaponId === 'awp') {
|
||||
// unscoped AWP: big circle = inaccurate
|
||||
return <div className="crosshair-circle" />
|
||||
}
|
||||
const gap = Math.min(40, st.me.spreadPx)
|
||||
const len = 9
|
||||
return (
|
||||
<div className="crosshair">
|
||||
<div className="ch-dot" />
|
||||
<div className="ch-line" style={{ left: '50%', top: '50%', width: 2, height: len, transform: `translate(-50%, ${gap}px)` }} />
|
||||
<div className="ch-line" style={{ left: '50%', top: '50%', width: 2, height: len, transform: `translate(-50%, ${-gap - len}px)` }} />
|
||||
<div className="ch-line" style={{ left: '50%', top: '50%', width: len, height: 2, transform: `translate(${gap}px, -50%)` }} />
|
||||
<div className="ch-line" style={{ left: '50%', top: '50%', width: len, height: 2, transform: `translate(${-gap - len}px, -50%)` }} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Scope() {
|
||||
return (
|
||||
<div className="scope">
|
||||
<div className="scope-cutout" />
|
||||
<div className="scope-hline" />
|
||||
<div className="scope-vline" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TeamTag({ team }: { team: Team }) {
|
||||
return <span className={`teamtag ${team === 'CT' ? 'ct' : 't'}`}>{team}</span>
|
||||
}
|
||||
|
||||
export default function HUD({ engine }: { engine: Engine }) {
|
||||
useSyncExternalStore(engine.hud.subscribe, engine.hud.getSnapshot)
|
||||
const st = engine.hud.state
|
||||
const now = engine.time
|
||||
|
||||
if (st.phase === 'menu') {
|
||||
return (
|
||||
<div className="overlay menu">
|
||||
<h1>DUST II</h1>
|
||||
<p className="subtitle">5v5 炸弹模式 · 三维第一人称 · 纯代码生成</p>
|
||||
<div className="team-select">
|
||||
<button className="team-card ct" onClick={() => { engine.start('CT'); engine.requestLock() }}>
|
||||
<span className="team-name">加入 CT</span>
|
||||
<span className="team-desc">反恐精英 · USP-S 起手<br />守卫 A / B 点,拆除 C4</span>
|
||||
</button>
|
||||
<button className="team-card t" onClick={() => { engine.start('T'); engine.requestLock() }}>
|
||||
<span className="team-name">加入 T</span>
|
||||
<span className="team-desc">恐怖分子 · Glock 起手<br />突入包点,安放 C4</span>
|
||||
</button>
|
||||
</div>
|
||||
<button className="spectate-btn" onClick={() => engine.start('CT', true)}>AI 对战观赏模式</button>
|
||||
<div className="controls-help">
|
||||
<span>WASD 移动</span><span>空格 跳跃</span><span>Shift 静步</span>
|
||||
<span>鼠标左键 开火</span><span>右键 AWP 开镜</span><span>R 换弹</span>
|
||||
<span>1/2/3 切枪</span><span>E 安放 / 拆除</span><span>B 购买阶段换 AWP</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const dmgFlash = now - st.damageAt < 0.4
|
||||
const hitAge = now - st.hitmarkerAt
|
||||
|
||||
return (
|
||||
<div className="hud">
|
||||
{st.me.scoped && <Scope />}
|
||||
<Crosshair st={st} />
|
||||
|
||||
{hitAge < 0.25 && (
|
||||
<div className={`hitmarker ${st.hitmarkerHead ? 'head' : ''}`}>
|
||||
<span /><span /><span /><span />
|
||||
</div>
|
||||
)}
|
||||
{dmgFlash && <div className="damage-flash" />}
|
||||
|
||||
{/* top bar */}
|
||||
<div className="topbar">
|
||||
<div className="score ct-score">{st.ctScore}</div>
|
||||
<div className="timer">
|
||||
{st.bombPlanted
|
||||
? <span className="bomb-timer">💣 {Math.ceil(st.bombTimeLeft ?? 0)}</span>
|
||||
: <span>{st.phase === 'freeze' ? fmtTime(st.freezeLeft) : fmtTime(st.timeLeft)}</span>}
|
||||
<div className="round-label">回合 {st.round}{st.pistolRound ? ' · 手枪局' : ''}</div>
|
||||
</div>
|
||||
<div className="score t-score">{st.tScore}</div>
|
||||
</div>
|
||||
|
||||
<Minimap engine={engine} />
|
||||
|
||||
{/* killfeed */}
|
||||
<div className="killfeed">
|
||||
{st.killfeed.map(k => (
|
||||
<div className="kf-entry" key={k.id}>
|
||||
<span className={k.attackerTeam === 'CT' ? 'kf-ct' : 'kf-t'}>{k.attacker}</span>
|
||||
<span className="kf-weapon">{k.weapon}{k.headshot ? ' ◎' : ''}</span>
|
||||
<span className={k.victimTeam === 'CT' ? 'kf-ct' : 'kf-t'}>{k.victim}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* bottom left: hp / armor */}
|
||||
{st.me.alive && (
|
||||
<div className="vitals">
|
||||
<div className="vital">
|
||||
<span className="vital-icon">+</span>
|
||||
<span className="vital-num">{st.me.hp}</span>
|
||||
<div className="vital-bar"><div style={{ width: `${st.me.hp}%` }} className="vital-fill hp" /></div>
|
||||
</div>
|
||||
<div className="vital">
|
||||
<span className="vital-icon">🛡</span>
|
||||
<span className="vital-num">{st.me.armor}</span>
|
||||
<div className="vital-bar"><div style={{ width: `${st.me.armor}%` }} className="vital-fill armor" /></div>
|
||||
</div>
|
||||
{st.me.hasBomb && <div className="carrying-bomb">携带 C4</div>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* bottom right: ammo */}
|
||||
{st.me.alive && (
|
||||
<div className="ammo">
|
||||
<div className="weapon-name">{st.me.weaponName}{st.me.reloading ? '(换弹中…)' : ''}</div>
|
||||
{st.me.weaponId !== 'knife' && (
|
||||
<div className="ammo-nums">
|
||||
<span className="ammo-mag">{st.me.mag}</span>
|
||||
<span className="ammo-sep">/</span>
|
||||
<span className="ammo-reserve">{st.me.reserve}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* center messages */}
|
||||
<div className="center-stack">
|
||||
{st.phase === 'freeze' && (
|
||||
<div className="banner freeze">
|
||||
<div className="banner-title">准备阶段 · {Math.ceil(st.freezeLeft)}s</div>
|
||||
{!st.pistolRound && <div className="banner-sub">按 B 切换主武器:当前 {st.loadout === 'awp' ? 'AWP' : '步枪'}</div>}
|
||||
{st.pistolRound && <div className="banner-sub">手枪局 — {st.playerTeam === 'T' ? 'Glock-18' : 'USP-S'},无护甲</div>}
|
||||
</div>
|
||||
)}
|
||||
{st.channel && (
|
||||
<div className="channel">
|
||||
<div className="channel-label">{st.channel.label}</div>
|
||||
<div className="channel-bar"><div style={{ width: `${st.channel.progress * 100}%` }} /></div>
|
||||
</div>
|
||||
)}
|
||||
{st.centerHint && !st.channel && <div className="hint">{st.centerHint}</div>}
|
||||
{st.bombPlanted && st.phase === 'live' && <div className="planted-note">C4 已安放</div>}
|
||||
{st.phase === 'over' && st.winner && (
|
||||
<div className={`banner winner ${st.winner === 'CT' ? 'ct' : 't'}`}>
|
||||
<div className="banner-title">{st.winner} 获胜</div>
|
||||
<div className="banner-sub">{st.winReason}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* spectate bar */}
|
||||
{st.spectating && st.phase === 'live' && (
|
||||
<div className="spectate-bar">
|
||||
{st.spectating.isSelf
|
||||
? <span>你已阵亡 — 正在寻找存活队友…</span>
|
||||
: <>
|
||||
<span>观战中:<b>{st.spectating.name}</b> <TeamTag team={st.me.team} /></span>
|
||||
<span className="spec-keys">←/→ 或 左键 切换 · E 接管该队友</span>
|
||||
</>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* pause overlay (pointer unlocked mid-game) */}
|
||||
{!st.locked && !st.auto && st.phase !== 'matchover' && (
|
||||
<div className="overlay pause" onClick={() => engine.requestLock()}>
|
||||
<div className="pause-box">
|
||||
<h2>已暂停 / 鼠标未锁定</h2>
|
||||
<p>点击任意处继续(锁定鼠标)</p>
|
||||
<button onClick={e => { e.stopPropagation(); engine.toMenu() }}>返回主菜单</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{st.phase === 'matchover' && st.matchWinner && (
|
||||
<div className="overlay">
|
||||
<div className="pause-box">
|
||||
<h2>比赛结束 — {st.matchWinner} {st.matchWinner === st.playerTeam ? '胜利 🏆' : '获胜'}</h2>
|
||||
<p>CT {st.ctScore} : {st.tScore} T</p>
|
||||
<button onClick={() => { engine.restartMatch(); engine.requestLock() }}>再来一局</button>
|
||||
<button onClick={() => engine.toMenu()}>返回主菜单</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
.app { position: relative; width: 100%; height: 100%; font-family: -apple-system, "PingFang SC", "Segoe UI", Roboto, sans-serif; user-select: none; }
|
||||
.viewport { position: absolute; inset: 0; }
|
||||
.viewport canvas { display: block; }
|
||||
|
||||
.hud { position: absolute; inset: 0; pointer-events: none; color: #eee; }
|
||||
.overlay { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; pointer-events: auto; }
|
||||
|
||||
/* ---------- menu ---------- */
|
||||
.menu { background: linear-gradient(180deg, rgba(10,14,20,0.55), rgba(10,14,20,0.82)); }
|
||||
.menu h1 { font-size: 84px; margin: 0; letter-spacing: 22px; color: #f2e3c2; text-shadow: 0 4px 24px rgba(0,0,0,0.6); font-weight: 800; }
|
||||
.menu .subtitle { color: #b8c4d0; letter-spacing: 4px; margin: 4px 0 42px; }
|
||||
.team-select { display: flex; gap: 28px; }
|
||||
.team-card { width: 250px; padding: 26px 22px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.18); cursor: pointer; text-align: left; transition: transform 0.12s, box-shadow 0.12s; color: #fff; }
|
||||
.team-card:hover { transform: translateY(-4px); box-shadow: 0 12px 34px rgba(0,0,0,0.5); }
|
||||
.team-card.ct { background: linear-gradient(160deg, #2c4a66, #1a2c3e); }
|
||||
.team-card.t { background: linear-gradient(160deg, #7a5c30, #4a3820); }
|
||||
.team-name { display: block; font-size: 26px; font-weight: 700; margin-bottom: 10px; }
|
||||
.team-desc { display: block; font-size: 13px; line-height: 1.7; color: rgba(255,255,255,0.75); }
|
||||
.spectate-btn { margin-top: 26px; background: none; border: 1px solid rgba(255,255,255,0.3); color: #b8c4d0; padding: 9px 26px; border-radius: 6px; cursor: pointer; font-size: 13px; }
|
||||
.spectate-btn:hover { border-color: #fff; color: #fff; }
|
||||
.controls-help { margin-top: 40px; display: flex; gap: 18px; flex-wrap: wrap; justify-content: center; max-width: 640px; color: #8a97a5; font-size: 12.5px; }
|
||||
.controls-help span { background: rgba(255,255,255,0.07); padding: 5px 12px; border-radius: 4px; }
|
||||
|
||||
/* ---------- crosshair ---------- */
|
||||
.crosshair { position: absolute; inset: 0; }
|
||||
.ch-dot { position: absolute; left: 50%; top: 50%; width: 2px; height: 2px; background: #4dff9d; transform: translate(-50%, -50%); box-shadow: 0 0 2px #000; }
|
||||
.ch-line { position: absolute; background: #4dff9d; box-shadow: 0 0 2px rgba(0,0,0,0.9); }
|
||||
.crosshair-circle { position: absolute; left: 50%; top: 50%; width: 120px; height: 120px; border: 1.5px dashed rgba(255,255,255,0.5); border-radius: 50%; transform: translate(-50%, -50%); }
|
||||
|
||||
/* ---------- scope ---------- */
|
||||
.scope { position: absolute; inset: 0; }
|
||||
.scope-cutout { position: absolute; inset: 0; background: radial-gradient(circle at center, transparent 0, transparent 33vmin, rgba(0,0,0,0.985) 34vmin); }
|
||||
.scope-hline { position: absolute; top: 50%; left: 0; right: 0; height: 1.5px; background: rgba(0,0,0,0.9); }
|
||||
.scope-vline { position: absolute; left: 50%; top: 0; bottom: 0; width: 1.5px; background: rgba(0,0,0,0.9); }
|
||||
|
||||
/* ---------- hitmarker / damage ---------- */
|
||||
.hitmarker span { position: absolute; left: 50%; top: 50%; width: 2.5px; height: 10px; background: #fff; }
|
||||
.hitmarker.head span { background: #ff5544; }
|
||||
.hitmarker span:nth-child(1) { transform: translate(-50%,-50%) rotate(45deg) translateY(-11px); }
|
||||
.hitmarker span:nth-child(2) { transform: translate(-50%,-50%) rotate(135deg) translateY(-11px); }
|
||||
.hitmarker span:nth-child(3) { transform: translate(-50%,-50%) rotate(225deg) translateY(-11px); }
|
||||
.hitmarker span:nth-child(4) { transform: translate(-50%,-50%) rotate(315deg) translateY(-11px); }
|
||||
.damage-flash { position: absolute; inset: 0; box-shadow: inset 0 0 140px 40px rgba(200,20,20,0.55); }
|
||||
|
||||
/* ---------- top bar ---------- */
|
||||
.topbar { position: absolute; top: 14px; left: 50%; transform: translateX(-50%); display: flex; align-items: stretch; gap: 2px; }
|
||||
.score { min-width: 58px; display: flex; align-items: center; justify-content: center; font-size: 30px; font-weight: 800; padding: 4px 10px; }
|
||||
.ct-score { background: rgba(58,110,165,0.85); border-radius: 6px 0 0 6px; }
|
||||
.t-score { background: rgba(196,142,54,0.85); border-radius: 0 6px 6px 0; }
|
||||
.timer { background: rgba(12,16,22,0.82); padding: 5px 18px; text-align: center; font-size: 21px; font-weight: 700; min-width: 96px; }
|
||||
.timer .bomb-timer { color: #ff7828; }
|
||||
.round-label { font-size: 10.5px; font-weight: 400; color: #93a1af; margin-top: 1px; }
|
||||
|
||||
/* ---------- minimap ---------- */
|
||||
.minimap { position: absolute; top: 14px; left: 14px; background: rgba(10,13,18,0.72); border: 1px solid rgba(255,255,255,0.14); border-radius: 6px; padding: 4px; }
|
||||
.minimap canvas { display: block; }
|
||||
.minimap canvas + canvas { position: absolute; top: 4px; left: 4px; }
|
||||
|
||||
/* ---------- killfeed ---------- */
|
||||
.killfeed { position: absolute; top: 14px; right: 14px; display: flex; flex-direction: column; gap: 5px; align-items: flex-end; }
|
||||
.kf-entry { background: rgba(10,13,18,0.78); border-radius: 4px; padding: 5px 11px; font-size: 13px; font-weight: 600; display: flex; gap: 8px; }
|
||||
.kf-ct { color: #6fa8dc; }
|
||||
.kf-t { color: #e0aa4e; }
|
||||
.kf-weapon { color: #cfd6dd; font-weight: 400; font-size: 12px; align-self: center; }
|
||||
|
||||
/* ---------- vitals ---------- */
|
||||
.vitals { position: absolute; left: 18px; bottom: 18px; display: flex; gap: 22px; align-items: flex-end; }
|
||||
.vital { background: rgba(10,13,18,0.72); border-radius: 6px; padding: 8px 14px; display: flex; align-items: center; gap: 9px; min-width: 130px; }
|
||||
.vital-icon { font-size: 15px; color: #aab7c4; }
|
||||
.vital-num { font-size: 26px; font-weight: 800; min-width: 44px; }
|
||||
.vital-bar { flex: 1; height: 4px; background: rgba(255,255,255,0.15); border-radius: 2px; overflow: hidden; min-width: 40px; }
|
||||
.vital-fill { height: 100%; border-radius: 2px; transition: width 0.15s; }
|
||||
.vital-fill.hp { background: #57c46a; }
|
||||
.vital-fill.armor { background: #6fa8dc; }
|
||||
.carrying-bomb { background: rgba(180,80,20,0.85); padding: 8px 14px; border-radius: 6px; font-weight: 700; font-size: 14px; }
|
||||
|
||||
/* ---------- ammo ---------- */
|
||||
.ammo { position: absolute; right: 18px; bottom: 18px; text-align: right; background: rgba(10,13,18,0.72); border-radius: 6px; padding: 10px 16px; }
|
||||
.weapon-name { font-size: 13px; color: #aab7c4; letter-spacing: 1px; margin-bottom: 2px; }
|
||||
.ammo-nums { font-weight: 800; }
|
||||
.ammo-mag { font-size: 34px; }
|
||||
.ammo-sep { font-size: 20px; color: #66727e; margin: 0 6px; }
|
||||
.ammo-reserve { font-size: 20px; color: #93a1af; }
|
||||
|
||||
/* ---------- center stack ---------- */
|
||||
.center-stack { position: absolute; left: 50%; top: 58%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 12px; }
|
||||
.banner { text-align: center; background: rgba(10,13,18,0.8); border-radius: 8px; padding: 12px 34px; }
|
||||
.banner-title { font-size: 24px; font-weight: 800; letter-spacing: 2px; }
|
||||
.banner-sub { font-size: 13px; color: #aab7c4; margin-top: 4px; }
|
||||
.banner.winner.ct .banner-title { color: #6fa8dc; }
|
||||
.banner.winner.t .banner-title { color: #e0aa4e; }
|
||||
.channel { width: 300px; background: rgba(10,13,18,0.8); border-radius: 8px; padding: 12px 18px; }
|
||||
.channel-label { font-size: 14px; font-weight: 700; margin-bottom: 8px; text-align: center; }
|
||||
.channel-bar { height: 8px; background: rgba(255,255,255,0.15); border-radius: 4px; overflow: hidden; }
|
||||
.channel-bar div { height: 100%; background: #ff7828; }
|
||||
.hint { background: rgba(10,13,18,0.8); padding: 8px 22px; border-radius: 6px; font-size: 15px; font-weight: 600; color: #ffd28a; }
|
||||
.planted-note { color: #ff7828; font-weight: 700; font-size: 13px; letter-spacing: 2px; text-shadow: 0 1px 4px #000; }
|
||||
|
||||
/* ---------- spectate ---------- */
|
||||
.spectate-bar { position: absolute; left: 50%; bottom: 90px; transform: translateX(-50%); background: rgba(10,13,18,0.85); border: 1px solid rgba(255,255,255,0.16); padding: 12px 26px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; gap: 5px; font-size: 15px; }
|
||||
.spec-keys { font-size: 12px; color: #93a1af; }
|
||||
.teamtag { font-size: 11px; font-weight: 800; padding: 1px 7px; border-radius: 3px; margin-left: 4px; }
|
||||
.teamtag.ct { background: #3a6ea5; }
|
||||
.teamtag.t { background: #c48e36; }
|
||||
|
||||
/* ---------- pause / matchover ---------- */
|
||||
.pause { background: rgba(6,8,11,0.6); cursor: pointer; }
|
||||
.pause-box { background: rgba(14,18,24,0.95); border: 1px solid rgba(255,255,255,0.15); border-radius: 12px; padding: 34px 48px; text-align: center; }
|
||||
.pause-box h2 { margin: 0 0 10px; font-size: 22px; }
|
||||
.pause-box p { color: #93a1af; margin: 0 0 20px; }
|
||||
.pause-box button { display: block; width: 100%; margin-top: 10px; background: rgba(255,255,255,0.1); color: #eee; border: 1px solid rgba(255,255,255,0.25); padding: 10px 0; border-radius: 6px; cursor: pointer; font-size: 14px; }
|
||||
.pause-box button:hover { background: rgba(255,255,255,0.2); }
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: { port: 5173 },
|
||||
})
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,370 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 0 1200 800" role="img" aria-labelledby="t">
|
||||
<title id="t">海边骑行的鹈鹕 · A pelican riding a bicycle by the sea</title>
|
||||
<defs>
|
||||
<linearGradient id="skyG" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="0" y2="420">
|
||||
<stop offset="0" stop-color="#2b7bc0"/>
|
||||
<stop offset=".45" stop-color="#7db9e2"/>
|
||||
<stop offset=".75" stop-color="#cfe7ef"/>
|
||||
<stop offset="1" stop-color="#ffe7c2"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="sunG" gradientUnits="userSpaceOnUse" cx="980" cy="128" r="150">
|
||||
<stop offset="0" stop-color="#fff9e0" stop-opacity=".95"/>
|
||||
<stop offset=".35" stop-color="#ffeab0" stop-opacity=".55"/>
|
||||
<stop offset="1" stop-color="#ffd98a" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="seaG" gradientUnits="userSpaceOnUse" x1="0" y1="418" x2="0" y2="612">
|
||||
<stop offset="0" stop-color="#0d5c86"/>
|
||||
<stop offset=".38" stop-color="#15799d"/>
|
||||
<stop offset=".7" stop-color="#2ba3ad"/>
|
||||
<stop offset="1" stop-color="#79dbc9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wetG" gradientUnits="userSpaceOnUse" x1="0" y1="596" x2="0" y2="800">
|
||||
<stop offset="0" stop-color="#ecd8b2"/>
|
||||
<stop offset=".45" stop-color="#d9b88c"/>
|
||||
<stop offset="1" stop-color="#c69d70"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="frameG" gradientUnits="userSpaceOnUse" x1="420" y1="440" x2="770" y2="620">
|
||||
<stop offset="0" stop-color="#f25549"/>
|
||||
<stop offset=".55" stop-color="#d63a2e"/>
|
||||
<stop offset="1" stop-color="#aa241c"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="seatG" gradientUnits="userSpaceOnUse" x1="474" y1="414" x2="550" y2="434">
|
||||
<stop offset="0" stop-color="#8a5a34"/>
|
||||
<stop offset="1" stop-color="#553318"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pelB" gradientUnits="userSpaceOnUse" x1="450" y1="430" x2="660" y2="220">
|
||||
<stop offset="0" stop-color="#cfc4b5"/>
|
||||
<stop offset=".45" stop-color="#efe9dd"/>
|
||||
<stop offset="1" stop-color="#fffdf4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pouchG" gradientUnits="userSpaceOnUse" x1="720" y1="225" x2="790" y2="320">
|
||||
<stop offset="0" stop-color="#ffc987"/>
|
||||
<stop offset=".55" stop-color="#f99b44"/>
|
||||
<stop offset="1" stop-color="#ef7a26"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="billG" gradientUnits="userSpaceOnUse" x1="710" y1="190" x2="850" y2="260">
|
||||
<stop offset="0" stop-color="#f6cf9c"/>
|
||||
<stop offset="1" stop-color="#db8e4e"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="scarfG" gradientUnits="userSpaceOnUse" x1="500" y1="280" x2="600" y2="312">
|
||||
<stop offset="0" stop-color="#f25549"/>
|
||||
<stop offset="1" stop-color="#a02020"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="reflFade" gradientUnits="userSpaceOnUse" x1="0" y1="678" x2="0" y2="802">
|
||||
<stop offset="0" stop-color="#fff" stop-opacity=".55"/>
|
||||
<stop offset="1" stop-color="#fff" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="vig" cx=".5" cy=".45" r=".75">
|
||||
<stop offset="0" stop-color="#0a1622" stop-opacity="0"/>
|
||||
<stop offset=".78" stop-color="#0a1622" stop-opacity="0"/>
|
||||
<stop offset="1" stop-color="#0a1622" stop-opacity=".22"/>
|
||||
</radialGradient>
|
||||
<mask id="reflMask">
|
||||
<rect x="250" y="678" width="760" height="130" fill="url(#reflFade)"/>
|
||||
</mask>
|
||||
<filter id="blur1"><feGaussianBlur stdDeviation="1"/></filter>
|
||||
<filter id="blur2"><feGaussianBlur stdDeviation="2"/></filter>
|
||||
<filter id="blur4"><feGaussianBlur stdDeviation="4"/></filter>
|
||||
<filter id="blur6"><feGaussianBlur stdDeviation="6"/></filter>
|
||||
<filter id="ripple" x="-15%" y="-15%" width="130%" height="130%">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.011 0.13" numOctaves="1" seed="7" result="n"/>
|
||||
<feDisplacementMap in="SourceGraphic" in2="n" scale="12" xChannelSelector="R" yChannelSelector="G"/>
|
||||
<feGaussianBlur stdDeviation="1.4"/>
|
||||
</filter>
|
||||
<filter id="grain">
|
||||
<feTurbulence type="fractalNoise" baseFrequency="0.8" numOctaves="2" seed="3"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.09 0.09 0.09 0 -0.02"/>
|
||||
</filter>
|
||||
<g id="wheel">
|
||||
<circle r="97" fill="none" stroke="#23262b" stroke-width="13"/>
|
||||
<circle r="97" fill="none" stroke="#3a3e45" stroke-width="2.5" stroke-dasharray="6 9" opacity=".55"/>
|
||||
<circle r="90" fill="none" stroke="#121417" stroke-width="1.6" opacity=".5"/>
|
||||
<circle r="84" fill="none" stroke="#ccd4da" stroke-width="5"/>
|
||||
<circle r="80" fill="none" stroke="#8d959c" stroke-width="1.2" opacity=".7"/>
|
||||
<g stroke="#b9c2c9" stroke-width="2" opacity=".9">
|
||||
<line x1="0" y1="-81" x2="0" y2="81"/>
|
||||
<line x1="0" y1="-81" x2="0" y2="81" transform="rotate(25.7)"/>
|
||||
<line x1="0" y1="-81" x2="0" y2="81" transform="rotate(51.4)"/>
|
||||
<line x1="0" y1="-81" x2="0" y2="81" transform="rotate(77.1)"/>
|
||||
<line x1="0" y1="-81" x2="0" y2="81" transform="rotate(102.9)"/>
|
||||
<line x1="0" y1="-81" x2="0" y2="81" transform="rotate(128.6)"/>
|
||||
<line x1="0" y1="-81" x2="0" y2="81" transform="rotate(154.3)"/>
|
||||
<line x1="0" y1="-84" x2="0" y2="-75" stroke="#6a7077" stroke-width="3.5"/>
|
||||
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="0.85s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
<circle r="11" fill="#9aa2a9" stroke="#5d646b" stroke-width="2"/>
|
||||
<circle r="4.5" fill="#3a3f44"/>
|
||||
</g>
|
||||
<g id="gull" fill="none" stroke="#46566a" stroke-width="3.5" stroke-linecap="round">
|
||||
<path d="M-16,0 Q-8,-9 0,-1 Q8,-9 16,0"/>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<!-- sky -->
|
||||
<rect x="0" y="0" width="1200" height="424" fill="url(#skyG)"/>
|
||||
<circle cx="980" cy="128" r="150" fill="url(#sunG)"/>
|
||||
<ellipse cx="980" cy="128" rx="95" ry="9" fill="#fff6d2" opacity=".45" filter="url(#blur4)"/>
|
||||
<circle cx="980" cy="128" r="33" fill="#fffdf0"/>
|
||||
<g filter="url(#blur4)" opacity=".95">
|
||||
<g>
|
||||
<ellipse cx="210" cy="108" rx="58" ry="26" fill="#fff"/>
|
||||
<ellipse cx="262" cy="116" rx="44" ry="20" fill="#fff"/>
|
||||
<ellipse cx="156" cy="118" rx="40" ry="18" fill="#fff"/>
|
||||
<ellipse cx="210" cy="128" rx="78" ry="12" fill="#dde8f0"/>
|
||||
<animateTransform attributeName="transform" type="translate" values="0 0; 26 0; 0 0" dur="56s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
<g>
|
||||
<ellipse cx="668" cy="84" rx="46" ry="20" fill="#fff"/>
|
||||
<ellipse cx="710" cy="92" rx="34" ry="15" fill="#fff"/>
|
||||
<ellipse cx="630" cy="92" rx="30" ry="13" fill="#fff"/>
|
||||
<ellipse cx="668" cy="99" rx="60" ry="9" fill="#dde8f0"/>
|
||||
<animateTransform attributeName="transform" type="translate" values="0 0; -20 0; 0 0" dur="64s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
</g>
|
||||
<ellipse cx="480" cy="388" rx="60" ry="8" fill="#fff" opacity=".32" filter="url(#blur2)"/>
|
||||
<ellipse cx="545" cy="398" rx="40" ry="6" fill="#fff" opacity=".22" filter="url(#blur2)"/>
|
||||
<g>
|
||||
<use href="#gull" transform="translate(262 160)" opacity=".85"/>
|
||||
<use href="#gull" transform="translate(330 128) scale(.7) rotate(-6)" opacity=".7"/>
|
||||
<use href="#gull" transform="translate(208 196) scale(.55) rotate(5)" opacity=".6"/>
|
||||
<animateTransform attributeName="transform" type="translate" values="0 0; 0 -5; 0 0" dur="4.6s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
<path d="M0,420 L0,396 C50,388 110,386 170,394 C200,398 220,406 232,420 Z" fill="#93aec4" opacity=".5" filter="url(#blur2)"/>
|
||||
<rect x="0" y="419" width="1200" height="2" fill="#f6efd8" opacity=".45"/>
|
||||
|
||||
<!-- sea -->
|
||||
<path d="M0,420 L1200,420 L1200,598 C1040,606 880,612 640,606 C420,612 200,610 0,618 Z" fill="url(#seaG)"/>
|
||||
<path d="M0,520 C180,510 350,526 560,518 C780,510 990,524 1200,514 L1200,560 C990,568 780,556 560,562 C350,570 180,556 0,564 Z" fill="#3db4b2" opacity=".45"/>
|
||||
<path d="M0,564 C160,556 330,570 540,562 C760,554 980,568 1200,558 L1200,596 C1040,604 880,608 640,602 C420,608 200,606 0,614 Z" fill="#6fd2c4" opacity=".55"/>
|
||||
<path d="M0,492 C200,484 420,498 640,490 C860,482 1040,496 1200,488" fill="none" stroke="#e9fbf6" stroke-width="2" opacity=".35"/>
|
||||
<path d="M0,536 C220,528 440,542 660,534 C880,526 1060,540 1200,532" fill="none" stroke="#e9fbf6" stroke-width="2" opacity=".28"/>
|
||||
<path d="M0,578 C240,570 460,584 680,576 C900,568 1080,582 1200,574" fill="none" stroke="#f2fdf9" stroke-width="2" opacity=".25"/>
|
||||
<g stroke="#fff6d0" stroke-linecap="round">
|
||||
<line x1="944" y1="436" x2="1006" y2="436" stroke-width="3" opacity=".85"/>
|
||||
<line x1="930" y1="448" x2="1022" y2="448" stroke-width="3.5" opacity=".7"/>
|
||||
<line x1="952" y1="460" x2="1000" y2="460" stroke-width="2.5" opacity=".8"/>
|
||||
<line x1="926" y1="474" x2="1030" y2="474" stroke-width="3.5" opacity=".6"/>
|
||||
<line x1="946" y1="488" x2="1008" y2="488" stroke-width="3" opacity=".7"/>
|
||||
<line x1="936" y1="503" x2="1018" y2="503" stroke-width="3.5" opacity=".55"/>
|
||||
<line x1="956" y1="517" x2="998" y2="517" stroke-width="2.5" opacity=".65"/>
|
||||
<line x1="930" y1="532" x2="1024" y2="532" stroke-width="3.5" opacity=".5"/>
|
||||
<line x1="948" y1="548" x2="1010" y2="548" stroke-width="3" opacity=".55"/>
|
||||
<line x1="938" y1="564" x2="1020" y2="564" stroke-width="3.5" opacity=".45"/>
|
||||
<line x1="956" y1="580" x2="1002" y2="580" stroke-width="2.5" opacity=".5"/>
|
||||
<line x1="944" y1="594" x2="1014" y2="594" stroke-width="3" opacity=".4"/>
|
||||
<animate attributeName="opacity" values=".75;1;.75" dur="3.6s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
<g opacity=".9">
|
||||
<path d="M1076,444 Q1090,452 1106,444 L1102,438 L1080,438 Z" fill="#3e4a55"/>
|
||||
<line x1="1091" y1="438" x2="1091" y2="404" stroke="#2e3840" stroke-width="1.5"/>
|
||||
<path d="M1091,406 L1091,436 L1072,436 Z" fill="#f8f3e6"/>
|
||||
<path d="M1094,410 L1094,436 L1110,438 Z" fill="#eee6d4"/>
|
||||
<line x1="1078" y1="456" x2="1104" y2="456" stroke="#3e4a55" stroke-width="3" opacity=".2" filter="url(#blur1)"/>
|
||||
</g>
|
||||
|
||||
<!-- beach -->
|
||||
<path d="M0,614 C220,608 440,610 660,602 C880,596 1050,600 1200,594 L1200,800 L0,800 Z" fill="url(#wetG)"/>
|
||||
<ellipse cx="600" cy="660" rx="560" ry="36" fill="#fff" opacity=".13" filter="url(#blur6)"/>
|
||||
<g stroke="#fff" stroke-linecap="round" filter="url(#blur2)">
|
||||
<line x1="180" y1="700" x2="500" y2="700" stroke-width="3" opacity=".12"/>
|
||||
<line x1="560" y1="724" x2="940" y2="724" stroke-width="3" opacity=".1"/>
|
||||
<line x1="300" y1="760" x2="580" y2="760" stroke-width="3" opacity=".08"/>
|
||||
</g>
|
||||
<path d="M0,613 Q50,605 100,611 Q150,616 200,609 Q250,601 300,608 Q350,614 400,607 Q450,599 500,605 Q550,612 600,603 Q650,596 700,601 Q750,608 800,599 Q850,592 900,598 Q950,605 1000,597 Q1050,590 1100,596 Q1150,602 1200,594" fill="none" stroke="#ffffff" stroke-width="6.5" opacity=".85" filter="url(#blur1)"/>
|
||||
<path d="M0,601 Q50,593 100,599 Q150,604 200,597 Q250,589 300,596 Q350,602 400,595 Q450,587 500,593 Q550,600 600,591 Q650,584 700,589 Q750,596 800,587 Q850,580 900,586 Q950,593 1000,585 Q1050,578 1100,584 Q1150,590 1200,582" fill="none" stroke="#ffffff" stroke-width="3" opacity=".4" stroke-dasharray="18 12" filter="url(#blur1)"/>
|
||||
<path d="M0,587 Q60,581 120,585 Q180,589 240,583 Q300,577 360,582 Q420,587 480,581 Q540,575 600,579 Q660,584 720,578 Q780,572 840,577 Q900,582 960,576 Q1020,570 1080,575 Q1140,580 1200,574" fill="none" stroke="#ffffff" stroke-width="2.5" opacity=".25" stroke-dasharray="8 14"/>
|
||||
<path d="M40,622 C120,616 210,624 300,620 C380,617 430,626 470,634 C430,648 340,656 250,652 C160,648 80,640 40,630 Z" fill="#ffffff" opacity=".18" filter="url(#blur2)"/>
|
||||
<path d="M0,800 L0,758 C180,742 400,768 640,780 C880,792 1060,786 1200,772 L1200,800 Z" fill="#f0d6a4"/>
|
||||
<path d="M0,758 C180,742 400,768 640,780 C880,792 1060,786 1200,772" fill="none" stroke="#f9e7bd" stroke-width="2" opacity=".6"/>
|
||||
<g stroke="#fff3cf" stroke-linecap="round" filter="url(#blur1)">
|
||||
<line x1="950" y1="616" x2="1008" y2="616" stroke-width="3" opacity=".25"/>
|
||||
<line x1="940" y1="634" x2="1016" y2="634" stroke-width="3" opacity=".16"/>
|
||||
<line x1="958" y1="652" x2="1000" y2="652" stroke-width="3" opacity=".1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M1000,720 L1007,735 L1022,737 L1010,747 L1014,762 L1000,753 L986,762 L990,747 L978,737 L993,735 Z" fill="#e8845a" stroke="#c4663e" stroke-width="1.5" stroke-linejoin="round" transform="rotate(-15 1000 742)"/>
|
||||
<circle cx="1000" cy="742" r="1.6" fill="#c4663e"/>
|
||||
<circle cx="1006" cy="733" r="1.2" fill="#c4663e"/>
|
||||
<circle cx="994" cy="733" r="1.2" fill="#c4663e"/>
|
||||
<path d="M156,768 Q170,734 184,768 Q170,778 156,768 Z" fill="#f2e3c8" stroke="#c9ab7e" stroke-width="1.2"/>
|
||||
<path d="M170,774 L162,748 M170,774 L170,742 M170,774 L178,748" stroke="#c9ab7e" stroke-width="1" opacity=".8" fill="none"/>
|
||||
<ellipse cx="240" cy="712" rx="5" ry="3" fill="#b59a78" opacity=".8"/>
|
||||
<ellipse cx="268" cy="716" rx="3" ry="2" fill="#a98e6c" opacity=".8"/>
|
||||
<ellipse cx="1105" cy="756" rx="6" ry="4" fill="#b59a78" opacity=".8"/>
|
||||
<ellipse cx="1130" cy="762" rx="4" ry="2.5" fill="#a98e6c" opacity=".8"/>
|
||||
<ellipse cx="420" cy="752" rx="4" ry="2.5" fill="#b59a78" opacity=".7"/>
|
||||
</g>
|
||||
<rect x="0" y="600" width="1200" height="200" fill="#5b4424" filter="url(#grain)" opacity=".5"/>
|
||||
<path d="M-20,708 C140,702 300,692 424,681" fill="none" stroke="#a8835a" stroke-width="8" opacity=".45"/>
|
||||
<path d="M-20,708 C140,702 300,692 424,681" fill="none" stroke="#c4a47b" stroke-width="2.5" opacity=".7" stroke-dasharray="10 6"/>
|
||||
<g stroke="#fff" stroke-linecap="round">
|
||||
<line x1="252" y1="318" x2="348" y2="318" stroke-width="4" opacity=".15"/>
|
||||
<line x1="232" y1="352" x2="306" y2="352" stroke-width="3" opacity=".12"/>
|
||||
<line x1="270" y1="290" x2="330" y2="290" stroke-width="3" opacity=".1"/>
|
||||
</g>
|
||||
<ellipse cx="577" cy="683" rx="250" ry="13" fill="#4a3520" opacity=".28" filter="url(#blur6)"/>
|
||||
<ellipse cx="410" cy="682" rx="55" ry="8" fill="#3c2c1a" opacity=".3" filter="url(#blur4)"/>
|
||||
<ellipse cx="748" cy="682" rx="55" ry="8" fill="#3c2c1a" opacity=".3" filter="url(#blur4)"/>
|
||||
|
||||
<g id="subject">
|
||||
<!-- far side: pedal, leg, crank -->
|
||||
<rect x="511" y="511" width="34" height="10" rx="3" fill="#1a1d21"/>
|
||||
<ellipse cx="522" cy="428" rx="14" ry="22" fill="#d8cec0" transform="rotate(8 522 428)"/>
|
||||
<path d="M521,452 Q513,478 524,502" fill="none" stroke="#c97a2e" stroke-width="10" stroke-linecap="round"/>
|
||||
<path d="M506,512 L527,498 L551,510 Q528,518 506,512 Z" fill="#cd7e30" stroke="#95571c" stroke-width=".8"/>
|
||||
<path d="M524,502 L540,509 M520,505 L532,511" stroke="#95571c" stroke-width="1" opacity=".7"/>
|
||||
<line x1="565" y1="582" x2="528" y2="516" stroke="#1d2125" stroke-width="8" stroke-linecap="round"/>
|
||||
<!-- wheels -->
|
||||
<use href="#wheel" transform="translate(425 578)"/>
|
||||
<use href="#wheel" transform="translate(762 578)"/>
|
||||
<circle cx="425" cy="578" r="15" fill="none" stroke="#6a7177" stroke-width="6"/>
|
||||
<circle cx="425" cy="578" r="18" fill="none" stroke="#545a60" stroke-width="2" stroke-dasharray="3 3"/>
|
||||
<!-- fenders -->
|
||||
<path d="M320.7,550.1 A108 108 0 0 1 529.3,550.1" fill="none" stroke="#f6efe0" stroke-width="11"/>
|
||||
<path d="M668.5,524 A108 108 0 0 1 863.5,541.1" fill="none" stroke="#f6efe0" stroke-width="11"/>
|
||||
<!-- chain -->
|
||||
<line x1="567" y1="553" x2="427" y2="564" stroke="#383d41" stroke-width="4" stroke-dasharray="5 3" opacity=".9"/>
|
||||
<path d="M427,592 C470,603 520,609 566,611" fill="none" stroke="#383d41" stroke-width="4" stroke-dasharray="5 3" opacity=".9"/>
|
||||
<!-- frame -->
|
||||
<line x1="565" y1="582" x2="425" y2="578" stroke="url(#frameG)" stroke-width="9" stroke-linecap="round"/>
|
||||
<line x1="518" y1="455" x2="427" y2="576" stroke="url(#frameG)" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="565" y1="582" x2="518" y2="452" stroke="url(#frameG)" stroke-width="10" stroke-linecap="round"/>
|
||||
<path d="M736,486 C678,524 618,560 567,582" fill="none" stroke="url(#frameG)" stroke-width="12" stroke-linecap="round"/>
|
||||
<path d="M519,454 C596,434 668,432 724,448" fill="none" stroke="url(#frameG)" stroke-width="10" stroke-linecap="round"/>
|
||||
<line x1="722" y1="442" x2="739" y2="489" stroke="#b32a20" stroke-width="12" stroke-linecap="round"/>
|
||||
<path d="M739,489 C748,520 756,548 762,578" fill="none" stroke="url(#frameG)" stroke-width="9" stroke-linecap="round"/>
|
||||
<path d="M521,450 C596,430 668,428 722,444" fill="none" stroke="#ffd9c9" stroke-width="2.5" opacity=".35"/>
|
||||
<path d="M733,488 C676,525 620,560 570,581" fill="none" stroke="#ffd9c9" stroke-width="2.5" opacity=".3"/>
|
||||
<circle cx="739" cy="489" r="5" fill="#8a8f94"/>
|
||||
<!-- saddle, stem, bars -->
|
||||
<line x1="518" y1="452" x2="512" y2="430" stroke="#c9ced3" stroke-width="7"/>
|
||||
<circle cx="488" cy="436" r="4" fill="none" stroke="#b9bec3" stroke-width="2.5"/>
|
||||
<circle cx="506" cy="438" r="4" fill="none" stroke="#b9bec3" stroke-width="2.5"/>
|
||||
<path d="M474,428 C478,418 496,414 516,416 C536,414 548,418 550,424 C540,432 520,434 504,433 C490,433 478,432 474,428 Z" fill="url(#seatG)" stroke="#4a2d17" stroke-width="1"/>
|
||||
<line x1="724" y1="444" x2="714" y2="409" stroke="#2e3338" stroke-width="8" stroke-linecap="round"/>
|
||||
<path d="M714,409 C736,390 724,382 700,390 C690,394 686,398 684,402" fill="none" stroke="#d7dce0" stroke-width="7" stroke-linecap="round"/>
|
||||
<path d="M696,392 C688,396 685,399 684,403" fill="none" stroke="#6b4226" stroke-width="9" stroke-linecap="round"/>
|
||||
<circle cx="719" cy="396" r="5" fill="#e2c45a" stroke="#9a7c28" stroke-width="1"/>
|
||||
<circle cx="719" cy="396" r="1.2" fill="#9a7c28"/>
|
||||
<!-- basket + fish -->
|
||||
<line x1="738" y1="478" x2="772" y2="472" stroke="#565b60" stroke-width="4"/>
|
||||
<path d="M800,420 C802,408 810,398 820,402 C814,408 812,414 812,420 Z" fill="#7fa8c4" stroke="#54748c" stroke-width="1"/>
|
||||
<ellipse cx="760" cy="414" rx="10" ry="7" fill="#8fb4cc" stroke="#54748c" stroke-width="1"/>
|
||||
<circle cx="757" cy="412" r="1.5" fill="#2c3e4c"/>
|
||||
<path d="M748,420 L824,420 L814,474 L758,474 Z" fill="#c89a5e" stroke="#8f6234" stroke-width="2" stroke-linejoin="round"/>
|
||||
<path d="M751,433 L821,433 M753,446 L818,446 M755,459 L816,459" stroke="#8f6234" stroke-width="1.6" opacity=".7"/>
|
||||
<path d="M758,421 L764,473 M770,421 L774,473 M782,420 L784,474 M794,420 L794,473 M806,420 L804,473 M818,420 L812,472" stroke="#8f6234" stroke-width="1.3" opacity=".5"/>
|
||||
<line x1="744" y1="420" x2="828" y2="420" stroke="#9a6b38" stroke-width="8" stroke-linecap="round"/>
|
||||
<line x1="746" y1="417" x2="826" y2="417" stroke="#d9b074" stroke-width="2" opacity=".8"/>
|
||||
<!-- chainring -->
|
||||
<circle cx="565" cy="582" r="33" fill="none" stroke="#565c62" stroke-width="2" stroke-dasharray="4 3.5"/>
|
||||
<circle cx="565" cy="582" r="30" fill="none" stroke="#4c5258" stroke-width="8"/>
|
||||
<circle cx="565" cy="582" r="17" fill="none" stroke="#4c5258" stroke-width="3"/>
|
||||
<line x1="565" y1="565" x2="565" y2="599" stroke="#4c5258" stroke-width="3"/>
|
||||
<line x1="548" y1="582" x2="582" y2="582" stroke="#4c5258" stroke-width="3"/>
|
||||
<circle cx="565" cy="582" r="6" fill="#33383d"/>
|
||||
|
||||
<!-- pelican -->
|
||||
<path d="M592,316 C636,330 666,356 682,382 C688,392 692,398 696,402 C688,402 678,396 668,384 C648,362 616,338 592,322 Z" fill="#cfc5b6"/>
|
||||
<path d="M676,380 C690,388 698,394 702,400 C694,403 686,398 680,392 C676,388 674,384 676,380 Z" fill="#1f1d1b"/>
|
||||
<path d="M442,330 C412,316 382,310 352,314 C380,324 408,332 440,342 Z" fill="#efe8db"/>
|
||||
<path d="M444,346 C410,338 378,338 350,346 C380,354 412,356 442,358 Z" fill="#e4dccc"/>
|
||||
<path d="M444,360 C412,360 384,366 362,376 C390,380 418,374 444,370 Z" fill="#d8cfbe"/>
|
||||
<path d="M446,310 C486,282 548,278 592,300 C606,308 614,322 616,338 C618,362 606,388 584,402 C556,418 508,420 478,408 C450,396 436,372 436,348 C436,332 440,320 446,310 Z" fill="url(#pelB)" stroke="#c9bdae" stroke-width="1" stroke-opacity=".25"/>
|
||||
<ellipse cx="588" cy="352" rx="26" ry="40" fill="#f4dfae" opacity=".5" filter="url(#blur4)" transform="rotate(-18 588 352)"/>
|
||||
<path d="M468,412 Q510,426 552,414" fill="none" stroke="#6b5a44" stroke-width="6" opacity=".25" filter="url(#blur4)"/>
|
||||
<path d="M470,402 Q520,416 572,402" fill="none" stroke="#b9ab9a" stroke-width="8" opacity=".3" filter="url(#blur4)"/>
|
||||
<g fill="none" stroke="#d6cbbb" stroke-width="1.8" opacity=".65">
|
||||
<path d="M468,396 Q480,404 494,398 Q508,406 522,399 Q536,406 550,398"/>
|
||||
<path d="M478,382 Q490,390 504,384 Q518,392 532,385 Q546,392 560,384"/>
|
||||
<path d="M490,368 Q502,376 516,370 Q530,378 544,371"/>
|
||||
</g>
|
||||
<g fill="none" stroke="#e8e0d2" stroke-width="1.6" opacity=".7">
|
||||
<path d="M474,316 Q500,306 528,304 M462,330 Q488,320 516,318"/>
|
||||
</g>
|
||||
<ellipse cx="560" cy="396" rx="30" ry="24" fill="#f7f2e7" transform="rotate(-15 560 396)"/>
|
||||
<path d="M540,404 Q554,410 568,404 M536,392 Q550,398 564,392" fill="none" stroke="#d6cbbb" stroke-width="1.6" opacity=".6"/>
|
||||
<g>
|
||||
<path d="M590,300 C558,286 524,280 494,286 C506,296 522,302 540,306 C528,312 512,314 498,312 C516,322 548,324 572,316 C582,312 588,306 590,300 Z" fill="url(#scarfG)"/>
|
||||
<path d="M588,306 C570,314 550,318 532,320 C550,324 568,320 582,314 Z" fill="#b32a28"/>
|
||||
<animateTransform attributeName="transform" type="rotate" values="-2.5 590 302; 2 590 302; -2.5 590 302" dur="1.6s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
<path d="M556,298 C600,256 628,232 658,198 C664,190 670,186 676,184 L690,224 C664,256 634,286 608,308 C596,318 582,320 572,316 C564,310 556,304 556,298 Z" fill="url(#pelB)"/>
|
||||
<path d="M560,296 C602,256 628,232 656,200" fill="none" stroke="#d8cec0" stroke-width="2" opacity=".55"/>
|
||||
<path d="M688,222 C662,254 634,284 608,306" fill="none" stroke="#ffe2a8" stroke-width="2.5" opacity=".7"/>
|
||||
<path d="M610,316 C616,336 614,362 600,386" fill="none" stroke="#ffe2a8" stroke-width="2.5" opacity=".6"/>
|
||||
<!-- bill + pouch -->
|
||||
<path d="M706,186 C758,202 810,228 851,251 C857,255 857,262 851,266 C806,244 758,228 710,214 C707,206 705,196 706,186 Z" fill="url(#billG)"/>
|
||||
<path d="M712,196 C762,210 812,233 848,254" fill="none" stroke="#b87844" stroke-width="1.6" opacity=".6"/>
|
||||
<path d="M710,189 C760,204 810,229 848,251" fill="none" stroke="#ffe3b0" stroke-width="2" opacity=".7"/>
|
||||
<line x1="722" y1="196" x2="740" y2="202" stroke="#9a6238" stroke-width="1.5" opacity=".6"/>
|
||||
<path d="M846,254 C853,256 855,262 849,266 C845,263 844,258 846,254 Z" fill="#c4452e"/>
|
||||
<path d="M714,214 C690,248 656,278 618,300 C640,314 690,320 744,316 C794,312 832,289 850,262 C802,242 758,226 714,214 Z" fill="url(#pouchG)"/>
|
||||
<path d="M706,238 C694,258 686,276 688,292 M718,242 C708,262 702,280 706,296" fill="none" stroke="#d96a28" stroke-width="2" opacity=".3"/>
|
||||
<path d="M710,218 C688,248 656,278 622,300" fill="none" stroke="#d4853a" stroke-width="2" opacity=".45"/>
|
||||
<path d="M712,213 C760,227 806,243 848,261" fill="none" stroke="#b97a44" stroke-width="2.5" opacity=".8"/>
|
||||
<path d="M622,302 C652,314 696,319 744,316 C794,312 830,290 846,266" fill="none" stroke="#ffd9a0" stroke-width="2" opacity=".75"/>
|
||||
<!-- head -->
|
||||
<circle cx="694" cy="200" r="28" fill="url(#pelB)"/>
|
||||
<path d="M678,183 C666,172 654,166 644,165 C654,173 662,181 669,190 Z" fill="#f6efe3"/>
|
||||
<path d="M674,192 C663,185 653,183 645,185 C654,190 662,196 668,202 Z" fill="#e9e0d0"/>
|
||||
<path d="M668,180 Q694,166 716,184" fill="none" stroke="#fff3cc" stroke-width="2" opacity=".6"/>
|
||||
<ellipse cx="701" cy="193" rx="9" ry="7" fill="#f2c98e" opacity=".8" transform="rotate(-12 701 193)"/>
|
||||
<circle cx="703" cy="192" r="4" fill="#2a2018"/>
|
||||
<circle cx="704.6" cy="190.6" r="1.3" fill="#fff" opacity=".95"/>
|
||||
<path d="M697,187 Q703,184.5 709,188.5" fill="none" stroke="#b89058" stroke-width="1.4"/>
|
||||
<path d="M578,292 L602,294 L594,316 Z" fill="#c93632" stroke="#8e1d1a" stroke-width="1"/>
|
||||
<!-- wing -->
|
||||
<path d="M556,308 C600,302 636,318 660,344" fill="none" stroke="#b5a896" stroke-width="5" opacity=".35" filter="url(#blur2)"/>
|
||||
<path d="M552,304 C604,296 646,316 670,348 C680,362 686,378 692,394 C686,398 678,396 672,388 C660,372 642,354 618,340 C596,328 570,318 552,304 Z" fill="#faf5ea" stroke="#d8cdbd" stroke-width=".8"/>
|
||||
<path d="M576,322 Q588,330 602,325 Q616,333 630,328 M596,338 Q610,346 624,341 Q638,349 652,344" fill="none" stroke="#d9d0c2" stroke-width="1.6" opacity=".65"/>
|
||||
<path d="M636,348 C656,366 672,384 682,400 C684,408 681,413 675,413 C666,403 654,388 642,371 C637,362 634,354 636,348 Z" fill="#36322d"/>
|
||||
<path d="M648,344 C672,360 690,378 700,394 C704,402 703,410 697,413 C687,404 675,390 663,374 C655,364 649,354 648,344 Z" fill="#26231f"/>
|
||||
<path d="M662,342 C684,354 700,368 710,382 C714,390 713,397 708,401 C698,391 686,377 672,362 C667,355 663,348 662,342 Z" fill="#1c1a17"/>
|
||||
<path d="M650,350 C668,366 684,382 696,398 M664,348 C682,362 696,376 706,390" fill="none" stroke="#cfc8bb" stroke-width="1" opacity=".35"/>
|
||||
<!-- near leg -->
|
||||
<path d="M566,426 Q592,520 598,622" fill="none" stroke="#ef9b40" stroke-width="11" stroke-linecap="round"/>
|
||||
<path d="M570,430 Q596,522 601,618" fill="none" stroke="#c87c2c" stroke-width="2.5" opacity=".45"/>
|
||||
<path d="M598,626 C590,634 584,640 582,646 L640,644 C622,634 608,628 598,626 Z" fill="#f6a649" stroke="#b86f24" stroke-width="1"/>
|
||||
<path d="M596,632 L622,643 M594,636 L610,644" stroke="#b86f24" stroke-width="1.2" opacity=".7"/>
|
||||
<path d="M640,644 q5,1 7,4" fill="none" stroke="#6b4216" stroke-width="1.5"/>
|
||||
<!-- near crank over leg -->
|
||||
<line x1="565" y1="582" x2="602" y2="648" stroke="#2e3338" stroke-width="9" stroke-linecap="round"/>
|
||||
<rect x="583" y="643" width="38" height="10" rx="3" fill="#16191c"/>
|
||||
<circle cx="602" cy="648" r="3" fill="#4a5056"/>
|
||||
</g>
|
||||
|
||||
<!-- reflection on wet sand -->
|
||||
<g opacity=".5" mask="url(#reflMask)" filter="url(#ripple)">
|
||||
<use href="#subject" transform="translate(0 1227.6) scale(1 -0.8)"/>
|
||||
</g>
|
||||
|
||||
<!-- kicked-up sand + drifting feather -->
|
||||
<g fill="#d9b787">
|
||||
<circle cx="388" cy="668" r="5" opacity=".7"/>
|
||||
<circle cx="372" cy="664" r="4" opacity=".7"/>
|
||||
<circle cx="358" cy="650" r="3" opacity=".6"/>
|
||||
<circle cx="404" cy="672" r="3.5" opacity=".6"/>
|
||||
<circle cx="346" cy="636" r="2.5" opacity=".45" filter="url(#blur1)"/>
|
||||
<circle cx="337" cy="622" r="2" opacity=".3" filter="url(#blur1)"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M336,452 C326,444 322,432 326,422 C330,430 336,440 342,448 C340,452 338,452 336,452 Z" fill="#f6f1e6"/>
|
||||
<path d="M338,450 C332,442 328,434 327,426" stroke="#d8d0c0" stroke-width="1" fill="none"/>
|
||||
<animateTransform attributeName="transform" type="translate" values="0 0; -18 30; 0 0" keyTimes="0; .5; 1" dur="6s" repeatCount="indefinite"/>
|
||||
<animate attributeName="opacity" values="0; .85; 0" keyTimes="0; .5; 1" dur="6s" repeatCount="indefinite"/>
|
||||
</g>
|
||||
|
||||
<!-- foreground palm fronds, defocused -->
|
||||
<g transform="translate(-12 -14)" filter="url(#blur1)" opacity=".97">
|
||||
<path d="M6,2 C96,4 192,18 286,58 C196,26 100,8 2,12 Z" fill="#1e5639"/>
|
||||
<path d="M2,6 C84,16 170,40 252,96 C172,56 84,26 -2,20 Z" fill="#17402d"/>
|
||||
<path d="M0,12 C70,26 150,58 224,128 C150,84 66,46 -4,30 Z" fill="#226044"/>
|
||||
<path d="M-2,18 C56,44 116,92 158,164 C108,108 52,58 -10,34 Z" fill="#163d2a"/>
|
||||
<path d="M-6,26 C30,62 64,118 80,190 C48,128 16,72 -16,42 Z" fill="#1d5034"/>
|
||||
<path d="M8,0 C70,-6 150,-2 220,18 C150,2 76,-2 6,8 Z" fill="#2a7050"/>
|
||||
<path d="M0,10 C80,22 160,52 224,124 M2,8 C84,18 170,42 250,94" fill="none" stroke="#0e2c1e" stroke-width="2" opacity=".5"/>
|
||||
</g>
|
||||
|
||||
<rect x="0" y="0" width="1200" height="800" fill="url(#vig)"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 27 KiB |
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,177 +0,0 @@
|
||||
<svg width="100%" viewBox="0 0 690 470" xmlns="http://www.w3.org/2000/svg" role="img" style="">
|
||||
<title style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">鹈鹕在海边骑自行车</title>
|
||||
<desc style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">金色黄昏下,一只白鹈鹕系着蓝围巾,骑着红色沙滩自行车沿海滩飞驰,嘴里叼着鱼,车篮里还有一条。</desc>
|
||||
<rect x="0" y="0" width="680" height="55" fill="#2c5484" style="fill:rgb(44, 84, 132);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="55" width="680" height="45" fill="#3d6c9a" style="fill:rgb(61, 108, 154);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="100" width="680" height="40" fill="#6a92b4" style="fill:rgb(106, 146, 180);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="140" width="680" height="32" fill="#9d9cb2" style="fill:rgb(157, 156, 178);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="172" width="680" height="28" fill="#d4a083" style="fill:rgb(212, 160, 131);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="200" width="680" height="26" fill="#eda066" style="fill:rgb(237, 160, 102);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="226" width="680" height="24" fill="#f8b475" style="fill:rgb(248, 180, 117);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="575" cy="130" r="72" fill="#ffd9a0" opacity="0.14" style="fill:rgb(255, 217, 160);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.14;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="575" cy="130" r="50" fill="#ffd9a0" opacity="0.22" style="fill:rgb(255, 217, 160);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.22;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="575" cy="130" r="33" fill="#ffd98c" style="fill:rgb(255, 217, 140);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="575" cy="130" r="24" fill="#fff0c8" opacity="0.9" style="fill:rgb(255, 240, 200);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="575" cy="130" r="13" fill="#fffaf0" opacity="0.9" style="fill:rgb(255, 250, 240);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="104" cy="74" rx="20" ry="11" fill="#fdf6ec" opacity="0.95" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.95;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="126" cy="66" rx="24" ry="13" fill="#fdf6ec" opacity="0.95" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.95;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="148" cy="74" rx="18" ry="10" fill="#fdf6ec" opacity="0.95" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.95;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="126" cy="79" rx="33" ry="6" fill="#f3cf9e" opacity="0.45" style="fill:rgb(243, 207, 158);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.45;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="288" cy="54" rx="16" ry="9" fill="#fdf6ec" opacity="0.95" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.95;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="308" cy="48" rx="20" ry="11" fill="#fdf6ec" opacity="0.95" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.95;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="328" cy="55" rx="14" ry="8" fill="#fdf6ec" opacity="0.95" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.95;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="308" cy="59" rx="26" ry="5" fill="#f3cf9e" opacity="0.45" style="fill:rgb(243, 207, 158);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.45;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="492" cy="88" rx="18" ry="9" fill="#fdf6ec" opacity="0.92" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.92;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="512" cy="82" rx="21" ry="11" fill="#fdf6ec" opacity="0.92" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.92;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="532" cy="89" rx="15" ry="8" fill="#fdf6ec" opacity="0.92" style="fill:rgb(253, 246, 236);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.92;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="512" cy="93" rx="28" ry="5" fill="#f5b97c" opacity="0.55" style="fill:rgb(245, 185, 124);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.55;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 150 104 q 7 -7 14 0 q 7 -7 14 0" fill="none" stroke="#3d4a5c" stroke-width="2" stroke-linecap="round" style="fill:none;stroke:rgb(61, 74, 92);color:rgb(255, 255, 255);stroke-width:2px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 208 84 q 6 -6 12 0 q 6 -6 12 0" fill="none" stroke="#3d4a5c" stroke-width="2" stroke-linecap="round" style="fill:none;stroke:rgb(61, 74, 92);color:rgb(255, 255, 255);stroke-width:2px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 322 64 q 5 -5 10 0 q 5 -5 10 0" fill="none" stroke="#3d4a5c" stroke-width="2" stroke-linecap="round" style="fill:none;stroke:rgb(61, 74, 92);color:rgb(255, 255, 255);stroke-width:2px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="250" width="680" height="9" fill="#ee9a64" style="fill:rgb(238, 154, 100);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="259" width="680" height="15" fill="#57a0ad" style="fill:rgb(87, 160, 173);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="274" width="680" height="20" fill="#2f8398" style="fill:rgb(47, 131, 152);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="0" y="294" width="680" height="34" fill="#226e84" style="fill:rgb(34, 110, 132);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="577" cy="285" rx="26" ry="30" fill="#ffce8e" opacity="0.15" style="fill:rgb(255, 206, 142);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.15;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g stroke="#ffe3ad" stroke-width="2.6" stroke-linecap="round" opacity="0.85" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:0.85;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<line x1="560" y1="258" x2="576" y2="258" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="572" y1="266" x2="584" y2="266" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="556" y1="274" x2="574" y2="274" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="580" y1="283" x2="590" y2="283" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="564" y1="292" x2="580" y2="292" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="576" y1="301" x2="588" y2="301" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="558" y1="309" x2="572" y2="309" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="584" y1="314" x2="592" y2="314" style="fill:rgb(0, 0, 0);stroke:rgb(255, 227, 173);color:rgb(255, 255, 255);stroke-width:2.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<path d="M 0 266 q 18 -5 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0" fill="none" stroke="#ffffff" stroke-width="1.6" opacity="0.3" style="fill:none;stroke:rgb(255, 255, 255);color:rgb(255, 255, 255);stroke-width:1.6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.3;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 8 284 q 18 -5 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0" fill="none" stroke="#ffffff" stroke-width="1.6" opacity="0.35" style="fill:none;stroke:rgb(255, 255, 255);color:rgb(255, 255, 255);stroke-width:1.6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.35;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 0 302 q 18 -5 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0" fill="none" stroke="#ffffff" stroke-width="1.6" opacity="0.3" style="fill:none;stroke:rgb(255, 255, 255);color:rgb(255, 255, 255);stroke-width:1.6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.3;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 6 312 q 18 -6 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0 t 36 0" fill="none" stroke="#ffffff" stroke-width="2.2" opacity="0.5" style="fill:none;stroke:rgb(255, 255, 255);color:rgb(255, 255, 255);stroke-width:2.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.5;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 76 246 L 116 246 L 108 254 L 84 254 Z" fill="#4a5568" style="fill:rgb(74, 85, 104);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="96" y1="246" x2="96" y2="214" stroke="#4a5568" stroke-width="1.5" style="fill:rgb(0, 0, 0);stroke:rgb(74, 85, 104);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 96 214 L 96 244 L 76 244 Z" fill="#f6efe2" style="fill:rgb(246, 239, 226);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 99 220 L 99 244 L 113 244 Z" fill="#f0bd8a" style="fill:rgb(240, 189, 138);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 0 330 L 680 330 L 680 352 C 500 360 180 360 0 352 Z" fill="#cfa172" style="fill:rgb(207, 161, 114);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 0 350 C 180 358 500 358 680 350 L 680 460 L 0 460 Z" fill="#eecf9a" style="fill:rgb(238, 207, 154);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 0 322 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 q 20 -8 40 0 L 680 334 L 0 334 Z" fill="#fdfbf5" opacity="0.92" style="fill:rgb(253, 251, 245);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.92;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 90 392 C 200 380 420 384 600 396 C 460 404 220 406 90 392 Z" fill="#e3bf85" opacity="0.7" style="fill:rgb(227, 191, 133);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.7;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g fill="#c69a5e" opacity="0.8" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.8;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<circle cx="60" cy="372" r="1.6" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="142" cy="448" r="1.8" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="226" cy="442" r="1.4" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="318" cy="450" r="1.7" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="402" cy="440" r="1.4" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="498" cy="446" r="1.9" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="572" cy="430" r="1.5" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="640" cy="376" r="1.7" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="528" cy="392" r="1.4" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="48" cy="416" r="1.5" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="612" cy="452" r="1.6" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="368" cy="364" r="1.3" style="fill:rgb(198, 154, 94);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<path d="M 78 414 L 83 424 L 94 425 L 86 432 L 89 443 L 78 437 L 67 443 L 70 432 L 62 425 L 73 424 Z" fill="#e87f58" style="fill:rgb(232, 127, 88);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="78" cy="429" r="1.2" fill="#c75f3c" style="fill:rgb(199, 95, 60);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="74" cy="425" r="1" fill="#c75f3c" style="fill:rgb(199, 95, 60);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><circle cx="82" cy="425" r="1" fill="#c75f3c" style="fill:rgb(199, 95, 60);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 596 420 Q 608 402 620 420 Z" fill="#f3e3c6" stroke="#c9a26b" stroke-width="1" style="fill:rgb(243, 227, 198);stroke:rgb(201, 162, 107);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="608" y1="419" x2="603" y2="408" stroke="#c9a26b" stroke-width="0.8" style="fill:rgb(0, 0, 0);stroke:rgb(201, 162, 107);color:rgb(255, 255, 255);stroke-width:0.8px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="608" y1="419" x2="608" y2="406" stroke="#c9a26b" stroke-width="0.8" style="fill:rgb(0, 0, 0);stroke:rgb(201, 162, 107);color:rgb(255, 255, 255);stroke-width:0.8px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="608" y1="419" x2="613" y2="408" stroke="#c9a26b" stroke-width="0.8" style="fill:rgb(0, 0, 0);stroke:rgb(201, 162, 107);color:rgb(255, 255, 255);stroke-width:0.8px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="348" cy="424" rx="172" ry="9" fill="#8a6536" opacity="0.35" style="fill:rgb(138, 101, 54);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.35;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g stroke="#b98e55" stroke-width="3.5" stroke-linecap="round" opacity="0.6" style="fill:rgb(0, 0, 0);stroke:rgb(185, 142, 85);color:rgb(255, 255, 255);stroke-width:3.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:0.6;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<line x1="96" y1="350" x2="152" y2="350" style="fill:rgb(0, 0, 0);stroke:rgb(185, 142, 85);color:rgb(255, 255, 255);stroke-width:3.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="80" y1="372" x2="146" y2="372" style="fill:rgb(0, 0, 0);stroke:rgb(185, 142, 85);color:rgb(255, 255, 255);stroke-width:3.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="102" y1="394" x2="158" y2="394" style="fill:rgb(0, 0, 0);stroke:rgb(185, 142, 85);color:rgb(255, 255, 255);stroke-width:3.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<circle cx="172" cy="414" r="5" fill="#f2dcae" opacity="0.85" style="fill:rgb(242, 220, 174);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.85;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="160" cy="420" r="3.5" fill="#f2dcae" opacity="0.85" style="fill:rgb(242, 220, 174);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.85;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="180" cy="421" r="3" fill="#f2dcae" opacity="0.85" style="fill:rgb(242, 220, 174);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.85;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="303" y="351" width="20" height="7" rx="2" fill="#26262a" style="fill:rgb(38, 38, 42);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="340" y1="380" x2="314" y2="356" stroke="#3c3c40" stroke-width="3.5" style="fill:rgb(0, 0, 0);stroke:rgb(60, 60, 64);color:rgb(255, 255, 255);stroke-width:3.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 312 294 C 310 316 308 334 310 352" fill="none" stroke="#cf7f1f" stroke-width="7" stroke-linecap="round" style="fill:none;stroke:rgb(207, 127, 31);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="312" cy="356" rx="10" ry="4" fill="#cf7f1f" style="fill:rgb(207, 127, 31);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<circle cx="235" cy="375" r="45" fill="none" stroke="#2e2a28" stroke-width="8" style="fill:none;stroke:rgb(46, 42, 40);color:rgb(255, 255, 255);stroke-width:8px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="235" cy="375" r="37" fill="none" stroke="#d6dae0" stroke-width="3" style="fill:none;stroke:rgb(214, 218, 224);color:rgb(255, 255, 255);stroke-width:3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="235" cy="375" r="33" fill="none" stroke="#9aa2ac" stroke-width="1.2" style="fill:none;stroke:rgb(154, 162, 172);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g stroke="#b9bec6" stroke-width="1.2" opacity="0.9" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<line x1="235" y1="375" x2="270" y2="381" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="235" y1="375" x2="256" y2="404" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="235" y1="375" x2="229" y2="410" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="235" y1="375" x2="206" y2="396" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="235" y1="375" x2="200" y2="369" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="235" y1="375" x2="214" y2="346" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="235" y1="375" x2="241" y2="340" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="235" y1="375" x2="264" y2="354" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<circle cx="235" cy="375" r="4.5" fill="#6f7780" style="fill:rgb(111, 119, 128);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="235" cy="375" r="6" fill="none" stroke="#4a4a52" stroke-width="2.5" style="fill:none;stroke:rgb(74, 74, 82);color:rgb(255, 255, 255);stroke-width:2.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<g style="fill:rgb(0, 0, 0);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<circle cx="455" cy="375" r="45" fill="none" stroke="#2e2a28" stroke-width="8" style="fill:none;stroke:rgb(46, 42, 40);color:rgb(255, 255, 255);stroke-width:8px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="455" cy="375" r="37" fill="none" stroke="#d6dae0" stroke-width="3" style="fill:none;stroke:rgb(214, 218, 224);color:rgb(255, 255, 255);stroke-width:3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="455" cy="375" r="33" fill="none" stroke="#9aa2ac" stroke-width="1.2" style="fill:none;stroke:rgb(154, 162, 172);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g stroke="#b9bec6" stroke-width="1.2" opacity="0.9" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<line x1="455" y1="375" x2="485" y2="394" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="455" y1="375" x2="463" y2="410" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="455" y1="375" x2="436" y2="405" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="455" y1="375" x2="420" y2="383" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="455" y1="375" x2="425" y2="356" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="455" y1="375" x2="447" y2="340" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="455" y1="375" x2="474" y2="345" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="455" y1="375" x2="490" y2="367" style="fill:rgb(0, 0, 0);stroke:rgb(185, 190, 198);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<circle cx="455" cy="375" r="4.5" fill="#6f7780" style="fill:rgb(111, 119, 128);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="455" cy="375" r="2" fill="#3c4148" style="fill:rgb(60, 65, 72);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<path d="M 186 357 A 52 52 0 0 1 284 357" fill="none" stroke="#d94f3d" stroke-width="6" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 406 357 A 52 52 0 0 1 504 357" fill="none" stroke="#d94f3d" stroke-width="6" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g stroke="#d94f3d" stroke-width="7" stroke-linecap="round" fill="none" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<path d="M 340 380 L 238 375" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 238 375 L 314 312" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 340 380 L 311 300" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 430 320 C 408 360 378 378 342 381" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 314 308 C 348 295 392 294 423 303" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<line x1="422" y1="298" x2="432" y2="322" stroke="#aa3a2b" stroke-width="8" stroke-linecap="round" style="fill:rgb(0, 0, 0);stroke:rgb(170, 58, 43);color:rgb(255, 255, 255);stroke-width:8px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 431 320 C 440 340 449 358 456 374" fill="none" stroke="#d94f3d" stroke-width="6" stroke-linecap="round" style="fill:none;stroke:rgb(217, 79, 61);color:rgb(255, 255, 255);stroke-width:6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="311" y1="300" x2="308" y2="290" stroke="#6f7780" stroke-width="5" stroke-linecap="round" style="fill:rgb(0, 0, 0);stroke:rgb(111, 119, 128);color:rgb(255, 255, 255);stroke-width:5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 292 288 C 300 281 318 281 326 290 C 318 295 300 296 292 288 Z" fill="#5b3a29" style="fill:rgb(91, 58, 41);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 297 286 C 304 283 314 283 320 287" fill="none" stroke="#7a5138" stroke-width="1.4" style="fill:none;stroke:rgb(122, 81, 56);color:rgb(255, 255, 255);stroke-width:1.4px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="423" y1="299" x2="417" y2="283" stroke="#6f7780" stroke-width="5" stroke-linecap="round" style="fill:rgb(0, 0, 0);stroke:rgb(111, 119, 128);color:rgb(255, 255, 255);stroke-width:5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 417 283 C 404 268 388 263 372 268" fill="none" stroke="#6f7780" stroke-width="5" stroke-linecap="round" style="fill:none;stroke:rgb(111, 119, 128);color:rgb(255, 255, 255);stroke-width:5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="378" y1="266" x2="366" y2="270" stroke="#3c3c40" stroke-width="7" stroke-linecap="round" style="fill:rgb(0, 0, 0);stroke:rgb(60, 60, 64);color:rgb(255, 255, 255);stroke-width:7px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="334" y1="370" x2="240" y2="370" stroke="#4a4a52" stroke-width="2" stroke-dasharray="3 2.4" style="fill:rgb(0, 0, 0);stroke:rgb(74, 74, 82);color:rgb(255, 255, 255);stroke-width:2px;stroke-dasharray:3px, 2.4px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="335" y1="390" x2="240" y2="381" stroke="#4a4a52" stroke-width="2" stroke-dasharray="3 2.4" style="fill:rgb(0, 0, 0);stroke:rgb(74, 74, 82);color:rgb(255, 255, 255);stroke-width:2px;stroke-dasharray:3px, 2.4px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="340" cy="380" r="11" fill="none" stroke="#6f7780" stroke-width="3" style="fill:none;stroke:rgb(111, 119, 128);color:rgb(255, 255, 255);stroke-width:3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="340" cy="380" r="4" fill="#6f7780" style="fill:rgb(111, 119, 128);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="340" y1="380" x2="366" y2="403" stroke="#4a4a52" stroke-width="4" stroke-linecap="round" style="fill:rgb(0, 0, 0);stroke:rgb(74, 74, 82);color:rgb(255, 255, 255);stroke-width:4px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<rect x="355" y="399" width="26" height="8" rx="2" fill="#2f2f33" style="fill:rgb(47, 47, 51);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="442" y1="300" x2="432" y2="314" stroke="#6f7780" stroke-width="2" style="fill:rgb(0, 0, 0);stroke:rgb(111, 119, 128);color:rgb(255, 255, 255);stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="482" y1="300" x2="468" y2="316" stroke="#6f7780" stroke-width="2" style="fill:rgb(0, 0, 0);stroke:rgb(111, 119, 128);color:rgb(255, 255, 255);stroke-width:2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 437 272 L 487 272 L 482 302 L 442 302 Z" fill="#d9a05b" stroke="#a9763c" stroke-width="1.5" style="fill:rgb(217, 160, 91);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1.5px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<g stroke="#a9763c" stroke-width="1" style="fill:rgb(0, 0, 0);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto">
|
||||
<line x1="440" y1="280" x2="484" y2="280" style="fill:rgb(0, 0, 0);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="441" y1="288" x2="483" y2="288" style="fill:rgb(0, 0, 0);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="442" y1="296" x2="482" y2="296" style="fill:rgb(0, 0, 0);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="449" y1="273" x2="448" y2="301" style="fill:rgb(0, 0, 0);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="461" y1="273" x2="461" y2="301" style="fill:rgb(0, 0, 0);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/><line x1="473" y1="273" x2="474" y2="301" style="fill:rgb(0, 0, 0);stroke:rgb(169, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</g>
|
||||
<line x1="435" y1="271" x2="489" y2="271" stroke="#b9824a" stroke-width="3.5" stroke-linecap="round" style="fill:rgb(0, 0, 0);stroke:rgb(185, 130, 74);color:rgb(255, 255, 255);stroke-width:3.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 448 264 C 458 257 470 257 478 264 C 470 270 458 270 448 264 Z" fill="#7fb3c8" style="fill:rgb(127, 179, 200);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 478 263 l 10 -6 l -1 7 l 7 4 l -12 2 Z" fill="#6fa7bd" style="fill:rgb(111, 167, 189);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="453" cy="263" r="1.4" fill="#23323a" style="fill:rgb(35, 50, 58);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 459 260 q 3 3 0 7" fill="none" stroke="#5d93a8" stroke-width="1" style="fill:none;stroke:rgb(93, 147, 168);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 262 250 L 228 232 L 234 248 L 262 258 Z" fill="#efe8d8" style="fill:rgb(239, 232, 216);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 262 260 L 224 252 L 232 266 L 262 268 Z" fill="#e2d9c4" style="fill:rgb(226, 217, 196);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 262 268 L 230 272 L 240 280 L 264 274 Z" fill="#d4cab2" style="fill:rgb(212, 202, 178);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 262 256 C 260 214 296 190 338 196 C 376 202 394 236 382 268 C 372 294 330 306 296 298 C 272 292 263 278 262 256 Z" fill="#f7f2e6" style="fill:rgb(247, 242, 230);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 266 262 C 272 288 308 302 350 292 C 318 308 278 300 266 262 Z" fill="#d9d0bd" style="fill:rgb(217, 208, 189);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 270 232 C 278 204 310 192 340 197 C 312 196 286 208 276 234 Z" fill="#ffffff" opacity="0.9" style="fill:rgb(255, 255, 255);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 344 198 C 372 206 390 234 382 264" fill="none" stroke="#ffcf94" stroke-width="3" stroke-linecap="round" opacity="0.85" style="fill:none;stroke:rgb(255, 207, 148);color:rgb(255, 255, 255);stroke-width:3px;stroke-linecap:round;stroke-linejoin:miter;opacity:0.85;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 300 212 C 332 200 364 210 376 234 C 384 252 376 272 356 282 C 338 290 314 288 300 276 C 290 264 290 226 300 212 Z" fill="#efe8d8" style="fill:rgb(239, 232, 216);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 312 224 C 336 218 358 226 368 244" fill="none" stroke="#c9bfa8" stroke-width="1.6" style="fill:none;stroke:rgb(201, 191, 168);color:rgb(255, 255, 255);stroke-width:1.6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 308 244 C 332 240 354 248 362 262" fill="none" stroke="#c9bfa8" stroke-width="1.6" style="fill:none;stroke:rgb(201, 191, 168);color:rgb(255, 255, 255);stroke-width:1.6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 308 262 C 328 262 346 268 354 276" fill="none" stroke="#c9bfa8" stroke-width="1.6" style="fill:none;stroke:rgb(201, 191, 168);color:rgb(255, 255, 255);stroke-width:1.6px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 304 274 L 272 296 L 286 298 L 308 282 Z" fill="#46464d" style="fill:rgb(70, 70, 77);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 310 280 L 284 302 L 298 302 L 318 288 Z" fill="#46464d" style="fill:rgb(70, 70, 77);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 322 212 C 330 170 352 158 370 142 C 380 132 392 122 404 116 L 408 132 C 396 136 386 146 379 156 C 366 174 352 186 346 214 Z" fill="#f7f2e6" style="fill:rgb(247, 242, 230);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 344 206 C 352 178 366 164 380 150" fill="none" stroke="#d9d0bd" stroke-width="3" opacity="0.8" style="fill:none;stroke:rgb(217, 208, 189);color:rgb(255, 255, 255);stroke-width:3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.8;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 380 152 C 388 142 396 134 405 128" fill="none" stroke="#ffcf94" stroke-width="2.5" stroke-linecap="round" opacity="0.9" style="fill:none;stroke:rgb(255, 207, 148);color:rgb(255, 255, 255);stroke-width:2.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="334" cy="210" r="5.5" fill="#2f5cab" style="fill:rgb(47, 92, 171);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 332 208 C 308 196 286 188 262 192 C 282 200 304 208 328 216 Z" fill="#3a6fc4" style="fill:rgb(58, 111, 196);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 330 214 C 312 214 294 220 280 230 C 296 228 314 224 332 222 Z" fill="#5585d2" style="fill:rgb(85, 133, 210);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 324 294 C 326 322 330 342 344 366 C 352 380 358 392 362 400" fill="none" stroke="#ef9b2d" stroke-width="8" stroke-linecap="round" style="fill:none;stroke:rgb(239, 155, 45);color:rgb(255, 255, 255);stroke-width:8px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 352 398 L 388 398 L 396 410 L 346 410 Z" fill="#ef9b2d" style="fill:rgb(239, 155, 45);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="362" y1="399" x2="360" y2="409" stroke="#cf7f1f" stroke-width="1.2" style="fill:rgb(0, 0, 0);stroke:rgb(207, 127, 31);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="372" y1="399" x2="372" y2="409" stroke="#cf7f1f" stroke-width="1.2" style="fill:rgb(0, 0, 0);stroke:rgb(207, 127, 31);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<line x1="381" y1="399" x2="384" y2="409" stroke="#cf7f1f" stroke-width="1.2" style="fill:rgb(0, 0, 0);stroke:rgb(207, 127, 31);color:rgb(255, 255, 255);stroke-width:1.2px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 342 220 C 360 226 372 242 376 260 C 378 268 374 272 368 272 C 362 272 358 266 356 258 C 352 244 346 234 338 228 Z" fill="#f3edde" style="fill:rgb(243, 237, 222);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 372 262 q 8 2 9 8" fill="none" stroke="#cfc6b2" stroke-width="1.4" style="fill:none;stroke:rgb(207, 198, 178);color:rgb(255, 255, 255);stroke-width:1.4px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 366 266 q 7 3 7 8" fill="none" stroke="#cfc6b2" stroke-width="1.4" style="fill:none;stroke:rgb(207, 198, 178);color:rgb(255, 255, 255);stroke-width:1.4px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<ellipse cx="412" cy="116" rx="18" ry="16" fill="#f7f2e6" style="fill:rgb(247, 242, 230);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 402 102 q -6 -8 -2 -16" fill="none" stroke="#f7f2e6" stroke-width="2.5" stroke-linecap="round" style="fill:none;stroke:rgb(247, 242, 230);color:rgb(255, 255, 255);stroke-width:2.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 408 100 q -3 -9 2 -14" fill="none" stroke="#f7f2e6" stroke-width="2.5" stroke-linecap="round" style="fill:none;stroke:rgb(247, 242, 230);color:rgb(255, 255, 255);stroke-width:2.5px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 422 108 L 430 104 L 430 118 L 423 117 Z" fill="#f2a13c" opacity="0.9" style="fill:rgb(242, 161, 60);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 427 108 C 458 110 496 120 521 131 L 519 138 C 494 128 458 119 427 117 Z" fill="#f2a13c" style="fill:rgb(242, 161, 60);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 429 111 C 462 113 498 123 518 133" fill="none" stroke="#d9821c" stroke-width="1.3" style="fill:none;stroke:rgb(217, 130, 28);color:rgb(255, 255, 255);stroke-width:1.3px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 521 131 q 5 3 2 8 l -5 -2" fill="#c96a12" style="fill:rgb(201, 106, 18);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 428 117 C 452 122 488 130 518 137 C 514 158 492 168 466 162 C 446 157 432 140 428 117 Z" fill="#f6a06a" style="fill:rgb(246, 160, 106);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 438 136 C 452 154 478 162 504 154 C 490 166 462 166 446 156 C 440 150 437 143 438 136 Z" fill="#ec8a4e" opacity="0.9" style="fill:rgb(236, 138, 78);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.9;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 444 128 C 456 142 476 150 498 148" fill="none" stroke="#d9763c" stroke-width="1" opacity="0.6" style="fill:none;stroke:rgb(217, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.6;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 438 122 C 448 136 468 146 492 145" fill="none" stroke="#d9763c" stroke-width="1" opacity="0.6" style="fill:none;stroke:rgb(217, 118, 60);color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:0.6;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 512 130 l 12 -9 l -1 9 l 9 5 l -13 3 Z" fill="#6fa7bd" style="fill:rgb(111, 167, 189);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<path d="M 413 102 q 6 -3 11 0" fill="none" stroke="#8f8672" stroke-width="1.6" stroke-linecap="round" style="fill:none;stroke:rgb(143, 134, 114);color:rgb(255, 255, 255);stroke-width:1.6px;stroke-linecap:round;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="419.5" cy="109" r="3.4" fill="#26262b" style="fill:rgb(38, 38, 43);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
<circle cx="418.3" cy="107.8" r="1.2" fill="#ffffff" style="fill:rgb(255, 255, 255);stroke:none;color:rgb(255, 255, 255);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;opacity:1;font-family:"Anthropic Sans", -apple-system, "system-ui", "Segoe UI", sans-serif;font-size:16px;font-weight:400;text-anchor:start;dominant-baseline:auto"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 77 KiB |
File diff suppressed because it is too large.
Load diff
@@ -1,437 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 640" width="960" height="640" role="img" aria-label="A pelican riding a bicycle along a seaside boardwalk at golden hour">
|
||||
<defs>
|
||||
<!-- Sky gradient: warm golden-hour seaside sky -->
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#7ec4e8"/>
|
||||
<stop offset="0.45" stop-color="#aedcf0"/>
|
||||
<stop offset="0.75" stop-color="#fbe3b8"/>
|
||||
<stop offset="1" stop-color="#ffd9a0"/>
|
||||
</linearGradient>
|
||||
<!-- Sea gradient -->
|
||||
<linearGradient id="sea" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#2e8fb8"/>
|
||||
<stop offset="0.5" stop-color="#3aa3c4"/>
|
||||
<stop offset="1" stop-color="#6cc6d8"/>
|
||||
</linearGradient>
|
||||
<!-- Sand gradient -->
|
||||
<linearGradient id="sand" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#f2d9a8"/>
|
||||
<stop offset="1" stop-color="#e3bd82"/>
|
||||
</linearGradient>
|
||||
<!-- Boardwalk gradient -->
|
||||
<linearGradient id="boardwalk" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#c89b66"/>
|
||||
<stop offset="1" stop-color="#a87b4a"/>
|
||||
</linearGradient>
|
||||
<!-- Pelican body shading -->
|
||||
<linearGradient id="bodyShade" x1="0" y1="0" x2="0.6" y2="1">
|
||||
<stop offset="0" stop-color="#ffffff"/>
|
||||
<stop offset="0.6" stop-color="#f2ece2"/>
|
||||
<stop offset="1" stop-color="#d9cdb8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wingShade" x1="0" y1="0" x2="0.3" y2="1">
|
||||
<stop offset="0" stop-color="#f7f2e8"/>
|
||||
<stop offset="0.7" stop-color="#e0d6c2"/>
|
||||
<stop offset="1" stop-color="#c4b69c"/>
|
||||
</linearGradient>
|
||||
<!-- Beak gradient -->
|
||||
<linearGradient id="beak" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#ffb24d"/>
|
||||
<stop offset="0.6" stop-color="#f8923a"/>
|
||||
<stop offset="1" stop-color="#e3702a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pouch" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#ffc97a"/>
|
||||
<stop offset="1" stop-color="#f09a48"/>
|
||||
</linearGradient>
|
||||
<!-- Sun glow -->
|
||||
<radialGradient id="sunGlow" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0" stop-color="#fff6d8" stop-opacity="0.95"/>
|
||||
<stop offset="0.35" stop-color="#ffe9a8" stop-opacity="0.6"/>
|
||||
<stop offset="1" stop-color="#ffe9a8" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<!-- Bike frame metal -->
|
||||
<linearGradient id="frame" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#e84855"/>
|
||||
<stop offset="1" stop-color="#b32638"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="cloudSoft" cx="0.5" cy="0.5" r="0.5">
|
||||
<stop offset="0" stop-color="#ffffff"/>
|
||||
<stop offset="1" stop-color="#ffffff" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- ====================== SKY ====================== -->
|
||||
<rect width="960" height="400" fill="url(#sky)"/>
|
||||
<circle cx="745" cy="138" r="120" fill="url(#sunGlow)"/>
|
||||
<circle cx="745" cy="138" r="42" fill="#fff3c4"/>
|
||||
<circle cx="745" cy="138" r="34" fill="#fffbe8"/>
|
||||
|
||||
<!-- Clouds -->
|
||||
<g fill="#ffffff" opacity="0.85">
|
||||
<ellipse cx="170" cy="95" rx="62" ry="17"/>
|
||||
<ellipse cx="205" cy="80" rx="44" ry="15"/>
|
||||
<ellipse cx="135" cy="82" rx="35" ry="12"/>
|
||||
</g>
|
||||
<g fill="#ffffff" opacity="0.7">
|
||||
<ellipse cx="480" cy="60" rx="50" ry="13"/>
|
||||
<ellipse cx="515" cy="48" rx="32" ry="11"/>
|
||||
</g>
|
||||
<g fill="#ffffff" opacity="0.6">
|
||||
<ellipse cx="860" cy="220" rx="46" ry="11"/>
|
||||
<ellipse cx="890" cy="208" rx="28" ry="9"/>
|
||||
</g>
|
||||
<ellipse cx="330" cy="150" rx="90" ry="26" fill="url(#cloudSoft)" opacity="0.8"/>
|
||||
|
||||
<!-- Distant gulls -->
|
||||
<g stroke="#5a6b78" stroke-width="2.4" fill="none" stroke-linecap="round">
|
||||
<path d="M 580 110 q 9 -9 18 0 q 9 -9 18 0"/>
|
||||
<path d="M 640 88 q 7 -7 14 0 q 7 -7 14 0"/>
|
||||
<path d="M 545 142 q 6 -6 12 0 q 6 -6 12 0"/>
|
||||
</g>
|
||||
|
||||
<!-- ====================== SEA ====================== -->
|
||||
<rect x="0" y="300" width="960" height="110" fill="url(#sea)"/>
|
||||
<!-- Horizon highlight -->
|
||||
<rect x="0" y="300" width="960" height="4" fill="#bfe8f2" opacity="0.8"/>
|
||||
<!-- Sun reflection path on water -->
|
||||
<g fill="#ffe9b0">
|
||||
<ellipse cx="745" cy="318" rx="58" ry="4" opacity="0.7"/>
|
||||
<ellipse cx="738" cy="330" rx="44" ry="3.6" opacity="0.6"/>
|
||||
<ellipse cx="752" cy="343" rx="50" ry="3.4" opacity="0.55"/>
|
||||
<ellipse cx="742" cy="356" rx="36" ry="3" opacity="0.5"/>
|
||||
<ellipse cx="748" cy="370" rx="42" ry="3" opacity="0.45"/>
|
||||
<ellipse cx="740" cy="384" rx="30" ry="2.6" opacity="0.4"/>
|
||||
</g>
|
||||
<!-- Wave glints -->
|
||||
<g stroke="#cdeef5" stroke-width="2.6" stroke-linecap="round" opacity="0.75" fill="none">
|
||||
<path d="M 50 326 q 16 -5 32 0"/>
|
||||
<path d="M 130 346 q 14 -4 28 0"/>
|
||||
<path d="M 235 322 q 18 -5 36 0"/>
|
||||
<path d="M 330 352 q 14 -4 28 0"/>
|
||||
<path d="M 420 330 q 16 -5 32 0"/>
|
||||
<path d="M 530 360 q 14 -4 28 0"/>
|
||||
<path d="M 620 336 q 15 -4 30 0"/>
|
||||
<path d="M 855 340 q 14 -4 28 0"/>
|
||||
<path d="M 90 372 q 15 -4 30 0"/>
|
||||
<path d="M 280 382 q 16 -5 32 0"/>
|
||||
<path d="M 480 388 q 14 -4 28 0"/>
|
||||
<path d="M 660 376 q 13 -4 26 0"/>
|
||||
</g>
|
||||
<!-- Sailboat -->
|
||||
<g>
|
||||
<path d="M 150 296 l 0 -34 l 22 32 z" fill="#fdfaf2"/>
|
||||
<path d="M 146 296 l 0 -26 l -14 24 z" fill="#f3e8d2"/>
|
||||
<path d="M 132 298 q 22 9 44 0 l -5 8 q -17 6 -34 0 z" fill="#c0392b"/>
|
||||
</g>
|
||||
|
||||
<!-- Surf foam where sea meets sand -->
|
||||
<path d="M 0 408 q 60 -10 120 0 t 120 0 t 120 0 t 120 0 t 120 0 t 120 0 t 120 0 t 120 0 V 424 H 0 Z" fill="#eaf7f5"/>
|
||||
<path d="M 0 414 q 80 -8 160 0 t 160 0 t 160 0 t 160 0 t 160 0 t 160 0 V 430 H 0 Z" fill="#f7ede0" opacity="0.9"/>
|
||||
|
||||
<!-- ====================== BEACH ====================== -->
|
||||
<rect x="0" y="420" width="960" height="100" fill="url(#sand)"/>
|
||||
<!-- sand speckles -->
|
||||
<g fill="#c79a5e" opacity="0.55">
|
||||
<circle cx="80" cy="450" r="2.2"/><circle cx="190" cy="470" r="1.8"/>
|
||||
<circle cx="300" cy="444" r="2"/><circle cx="420" cy="478" r="1.7"/>
|
||||
<circle cx="560" cy="452" r="2.1"/><circle cx="700" cy="468" r="1.8"/>
|
||||
<circle cx="840" cy="446" r="2"/><circle cx="910" cy="476" r="1.6"/>
|
||||
<circle cx="140" cy="488" r="1.6"/><circle cx="640" cy="486" r="1.7"/>
|
||||
</g>
|
||||
<!-- Starfish -->
|
||||
<g transform="translate(118 470) scale(0.9)" fill="#e76f51">
|
||||
<path d="M 0 -14 L 4 -4 L 14 -3 L 6 4 L 9 14 L 0 8 L -9 14 L -6 4 L -14 -3 L -4 -4 Z"/>
|
||||
<circle cx="0" cy="0" r="2.4" fill="#f4a261"/>
|
||||
</g>
|
||||
<!-- Seashells -->
|
||||
<g transform="translate(835 462)">
|
||||
<path d="M 0 0 a 11 11 0 0 1 22 0 z" fill="#f6e7d7"/>
|
||||
<path d="M 4 0 a 7 7 0 0 1 14 0" fill="none" stroke="#d8b394" stroke-width="1.6"/>
|
||||
<path d="M 11 -10 L 11 0" stroke="#d8b394" stroke-width="1.4"/>
|
||||
</g>
|
||||
<!-- Beach grass tuft -->
|
||||
<g stroke="#7da45c" stroke-width="3" stroke-linecap="round" fill="none">
|
||||
<path d="M 52 510 q -4 -26 -14 -38"/>
|
||||
<path d="M 56 510 q 0 -30 4 -42"/>
|
||||
<path d="M 60 510 q 6 -24 18 -34"/>
|
||||
</g>
|
||||
<g stroke="#8fb86d" stroke-width="3" stroke-linecap="round" fill="none">
|
||||
<path d="M 906 512 q -3 -22 -12 -32"/>
|
||||
<path d="M 910 512 q 2 -26 8 -36"/>
|
||||
</g>
|
||||
|
||||
<!-- ====================== BOARDWALK ====================== -->
|
||||
<rect x="0" y="520" width="960" height="120" fill="url(#boardwalk)"/>
|
||||
<g stroke="#8a6038" stroke-width="3">
|
||||
<line x1="0" y1="548" x2="960" y2="548"/>
|
||||
<line x1="0" y1="578" x2="960" y2="578"/>
|
||||
<line x1="0" y1="610" x2="960" y2="610"/>
|
||||
</g>
|
||||
<g stroke="#8a6038" stroke-width="2.4" opacity="0.85">
|
||||
<line x1="90" y1="520" x2="78" y2="548"/>
|
||||
<line x1="250" y1="520" x2="240" y2="548"/>
|
||||
<line x1="430" y1="520" x2="422" y2="548"/>
|
||||
<line x1="610" y1="520" x2="602" y2="548"/>
|
||||
<line x1="790" y1="520" x2="782" y2="548"/>
|
||||
<line x1="170" y1="548" x2="160" y2="578"/>
|
||||
<line x1="340" y1="548" x2="332" y2="578"/>
|
||||
<line x1="520" y1="548" x2="512" y2="578"/>
|
||||
<line x1="700" y1="548" x2="692" y2="578"/>
|
||||
<line x1="880" y1="548" x2="872" y2="578"/>
|
||||
<line x1="120" y1="578" x2="110" y2="610"/>
|
||||
<line x1="300" y1="578" x2="292" y2="610"/>
|
||||
<line x1="480" y1="578" x2="472" y2="610"/>
|
||||
<line x1="660" y1="578" x2="652" y2="610"/>
|
||||
<line x1="840" y1="578" x2="832" y2="610"/>
|
||||
</g>
|
||||
<!-- wood grain hints -->
|
||||
<g stroke="#9c7044" stroke-width="1.6" opacity="0.6" fill="none">
|
||||
<path d="M 30 534 q 50 3 100 0"/>
|
||||
<path d="M 560 562 q 60 4 120 0"/>
|
||||
<path d="M 200 596 q 70 3 140 0"/>
|
||||
<path d="M 720 534 q 55 3 110 0"/>
|
||||
</g>
|
||||
|
||||
<!-- ====================== SHADOW ====================== -->
|
||||
<ellipse cx="468" cy="566" rx="225" ry="17" fill="#5b3d22" opacity="0.30"/>
|
||||
|
||||
<!-- ====================== BICYCLE + PELICAN ====================== -->
|
||||
<g>
|
||||
<!-- ===== Rear wheel ===== -->
|
||||
<g transform="translate(330 510)">
|
||||
<circle r="58" fill="none" stroke="#2b2b2b" stroke-width="9"/>
|
||||
<circle r="58" fill="none" stroke="#1a1a1a" stroke-width="3" opacity="0.5"/>
|
||||
<circle r="49" fill="none" stroke="#b8b8b8" stroke-width="3"/>
|
||||
<g stroke="#9aa3a8" stroke-width="2">
|
||||
<line x1="0" y1="0" x2="46" y2="12"/>
|
||||
<line x1="0" y1="0" x2="-46" y2="-12"/>
|
||||
<line x1="0" y1="0" x2="12" y2="-46"/>
|
||||
<line x1="0" y1="0" x2="-12" y2="46"/>
|
||||
<line x1="0" y1="0" x2="40" y2="-26"/>
|
||||
<line x1="0" y1="0" x2="-40" y2="26"/>
|
||||
<line x1="0" y1="0" x2="26" y2="40"/>
|
||||
<line x1="0" y1="0" x2="-26" y2="-40"/>
|
||||
</g>
|
||||
<circle r="7.5" fill="#454545"/>
|
||||
<circle r="3.4" fill="#8d9499"/>
|
||||
</g>
|
||||
|
||||
<!-- ===== Front wheel ===== -->
|
||||
<g transform="translate(610 510)">
|
||||
<circle r="58" fill="none" stroke="#2b2b2b" stroke-width="9"/>
|
||||
<circle r="58" fill="none" stroke="#1a1a1a" stroke-width="3" opacity="0.5"/>
|
||||
<circle r="49" fill="none" stroke="#b8b8b8" stroke-width="3"/>
|
||||
<g stroke="#9aa3a8" stroke-width="2">
|
||||
<line x1="0" y1="0" x2="44" y2="18"/>
|
||||
<line x1="0" y1="0" x2="-44" y2="-18"/>
|
||||
<line x1="0" y1="0" x2="18" y2="-44"/>
|
||||
<line x1="0" y1="0" x2="-18" y2="44"/>
|
||||
<line x1="0" y1="0" x2="36" y2="-31"/>
|
||||
<line x1="0" y1="0" x2="-36" y2="31"/>
|
||||
<line x1="0" y1="0" x2="31" y2="36"/>
|
||||
<line x1="0" y1="0" x2="-31" y2="-36"/>
|
||||
</g>
|
||||
<circle r="7.5" fill="#454545"/>
|
||||
<circle r="3.4" fill="#8d9499"/>
|
||||
</g>
|
||||
|
||||
<!-- motion speed lines behind -->
|
||||
<g stroke="#9fb6bf" stroke-width="4" stroke-linecap="round" opacity="0.55">
|
||||
<line x1="186" y1="462" x2="246" y2="462"/>
|
||||
<line x1="166" y1="492" x2="238" y2="492"/>
|
||||
<line x1="190" y1="522" x2="248" y2="522"/>
|
||||
</g>
|
||||
|
||||
<!-- ===== Frame ===== -->
|
||||
<g stroke="url(#frame)" stroke-width="11" stroke-linecap="round" fill="none">
|
||||
<!-- chainstay -->
|
||||
<line x1="330" y1="510" x2="455" y2="505"/>
|
||||
<!-- seat tube -->
|
||||
<line x1="455" y1="505" x2="425" y2="408"/>
|
||||
<!-- seat stay -->
|
||||
<line x1="330" y1="510" x2="425" y2="408"/>
|
||||
<!-- top tube -->
|
||||
<line x1="425" y1="408" x2="572" y2="420"/>
|
||||
<!-- down tube -->
|
||||
<line x1="455" y1="505" x2="572" y2="420"/>
|
||||
<!-- head tube + fork -->
|
||||
<line x1="572" y1="420" x2="585" y2="392"/>
|
||||
<line x1="572" y1="420" x2="610" y2="510"/>
|
||||
</g>
|
||||
<!-- frame highlight -->
|
||||
<g stroke="#ff8c94" stroke-width="3" stroke-linecap="round" opacity="0.7" fill="none">
|
||||
<line x1="430" y1="404" x2="566" y2="416"/>
|
||||
</g>
|
||||
|
||||
<!-- ===== Crank & chain ===== -->
|
||||
<circle cx="455" cy="505" r="17" fill="none" stroke="#3a3a3a" stroke-width="5"/>
|
||||
<circle cx="455" cy="505" r="6" fill="#3a3a3a"/>
|
||||
<g stroke="#4a4a4a" stroke-width="3.4" fill="none" opacity="0.9">
|
||||
<line x1="330" y1="498" x2="455" y2="488"/>
|
||||
<line x1="330" y1="522" x2="455" y2="521"/>
|
||||
</g>
|
||||
|
||||
<!-- ===== Handlebar ===== -->
|
||||
<g stroke="#3a3a3a" stroke-width="8" stroke-linecap="round" fill="none">
|
||||
<path d="M 585 392 q 4 -22 26 -26"/>
|
||||
</g>
|
||||
<circle cx="611" cy="366" r="6.6" fill="#7a4a26"/>
|
||||
|
||||
<!-- Wicker basket on handlebar with a fish -->
|
||||
<g>
|
||||
<path d="M 622 380 l 46 0 l -7 34 l -32 0 z" fill="#c98f4e" stroke="#9c6a33" stroke-width="3"/>
|
||||
<g stroke="#9c6a33" stroke-width="2">
|
||||
<line x1="625" y1="390" x2="666" y2="390"/>
|
||||
<line x1="627" y1="400" x2="664" y2="400"/>
|
||||
<line x1="634" y1="381" x2="632" y2="413"/>
|
||||
<line x1="646" y1="381" x2="645" y2="413"/>
|
||||
<line x1="658" y1="381" x2="657" y2="413"/>
|
||||
</g>
|
||||
<!-- fish peeking out -->
|
||||
<g transform="translate(648 374) rotate(-18)">
|
||||
<ellipse cx="0" cy="0" rx="17" ry="7" fill="#79b5d6"/>
|
||||
<path d="M 14 0 l 11 -7 l -3 7 l 3 7 z" fill="#5d9cbf"/>
|
||||
<circle cx="-9" cy="-1.6" r="1.8" fill="#22303a"/>
|
||||
<path d="M -3 -6 q 4 6 0 12" stroke="#5d9cbf" stroke-width="1.6" fill="none"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- ===== Saddle ===== -->
|
||||
<line x1="425" y1="408" x2="418" y2="386" stroke="#3a3a3a" stroke-width="7"/>
|
||||
<path d="M 392 386 q 26 -11 54 -2 q -8 12 -26 12 q -20 0 -28 -10 z" fill="#54341c"/>
|
||||
<path d="M 396 384 q 22 -8 44 -2" stroke="#7a5230" stroke-width="2.4" fill="none"/>
|
||||
|
||||
<!-- ===================== PELICAN ===================== -->
|
||||
<!-- Far leg (behind body) to rear pedal position -->
|
||||
<g stroke="#d8862e" stroke-width="11" stroke-linecap="round" fill="none" opacity="0.85">
|
||||
<path d="M 430 430 q -8 36 14 56"/>
|
||||
</g>
|
||||
<!-- far webbed foot on up-pedal -->
|
||||
<g transform="translate(449 489)">
|
||||
<line x1="-14" y1="-1" x2="6" y2="-1" stroke="#3a3a3a" stroke-width="5"/>
|
||||
<path d="M -16 -8 l 26 0 l 9 9 l -36 0 z" fill="#d8862e" opacity="0.85"/>
|
||||
</g>
|
||||
|
||||
<!-- Tail feathers -->
|
||||
<path d="M 372 392 q -42 -2 -64 22 q 28 4 44 -2 q -18 12 -26 26 q 28 -4 46 -22 z" fill="url(#wingShade)" stroke="#b6a888" stroke-width="2"/>
|
||||
|
||||
<!-- Body -->
|
||||
<path d="M 372 386
|
||||
q -16 22 -6 44
|
||||
q 10 24 44 30
|
||||
q 36 6 64 -12
|
||||
q 24 -16 30 -44
|
||||
q 4 -22 -8 -36
|
||||
q -22 -22 -62 -16
|
||||
q -42 6 -62 34 z" fill="url(#bodyShade)" stroke="#c9bda6" stroke-width="2.5"/>
|
||||
|
||||
<!-- Belly shading -->
|
||||
<path d="M 386 442 q 30 18 64 4 q -28 22 -58 8 q -8 -5 -6 -12 z" fill="#cfc3ac" opacity="0.8"/>
|
||||
|
||||
<!-- Wing folded over body, slightly lifted as if balancing -->
|
||||
<path d="M 398 392
|
||||
q 38 -18 76 -6
|
||||
q 26 9 28 30
|
||||
q -10 22 -42 26
|
||||
q -34 4 -56 -12
|
||||
q -18 -14 -6 -38 z" fill="url(#wingShade)" stroke="#bfb29a" stroke-width="2.5"/>
|
||||
<!-- wing feather lines -->
|
||||
<g stroke="#b6a888" stroke-width="2.2" fill="none" stroke-linecap="round">
|
||||
<path d="M 420 398 q 36 -8 66 6"/>
|
||||
<path d="M 416 412 q 38 -6 70 8"/>
|
||||
<path d="M 420 426 q 34 -2 60 8"/>
|
||||
</g>
|
||||
<!-- wingtip dark feathers -->
|
||||
<path d="M 486 436 q 14 4 16 -6 q 10 12 -4 18 q -12 5 -12 -12 z" fill="#8e8270"/>
|
||||
|
||||
<!-- Neck: graceful S-curve leaning forward toward the bars -->
|
||||
<path d="M 470 380
|
||||
q -4 -42 24 -64
|
||||
q 22 -17 44 -12
|
||||
q 16 4 18 22
|
||||
q 2 18 -12 24
|
||||
q -16 8 -24 26
|
||||
q -7 16 -4 30
|
||||
q -22 12 -46 2
|
||||
q -4 -16 0 -28 z" fill="url(#bodyShade)" stroke="#c9bda6" stroke-width="2.5"/>
|
||||
|
||||
<!-- Head -->
|
||||
<circle cx="546" cy="318" r="29" fill="url(#bodyShade)" stroke="#c9bda6" stroke-width="2.5"/>
|
||||
<!-- head crest tuft -->
|
||||
<path d="M 528 296 q 6 -14 20 -16 q -4 8 -2 12 q 8 -8 18 -6 q -8 6 -10 12 q -14 -8 -26 -2 z" fill="#e8e0cf"/>
|
||||
|
||||
<!-- Eye -->
|
||||
<circle cx="558" cy="312" r="6.8" fill="#ffffff"/>
|
||||
<circle cx="560" cy="313" r="4.4" fill="#2c2418"/>
|
||||
<circle cx="561.6" cy="311.2" r="1.6" fill="#ffffff"/>
|
||||
<!-- eye ring (pelicans have bare orbital skin) -->
|
||||
<circle cx="558" cy="312" r="9.4" fill="none" stroke="#f0b86a" stroke-width="2.2"/>
|
||||
|
||||
<!-- ===== Beak with pouch (signature pelican feature) ===== -->
|
||||
<!-- gular pouch -->
|
||||
<path d="M 566 326
|
||||
q 36 10 64 34
|
||||
q 14 12 10 22
|
||||
q -6 12 -24 6
|
||||
q -28 -10 -46 -28
|
||||
q -12 -12 -12 -24 z" fill="url(#pouch)" stroke="#d97a2c" stroke-width="2.4"/>
|
||||
<!-- pouch crease lines -->
|
||||
<g stroke="#e08a3c" stroke-width="1.8" fill="none" opacity="0.8">
|
||||
<path d="M 574 336 q 28 12 52 34"/>
|
||||
<path d="M 570 346 q 22 12 40 28"/>
|
||||
</g>
|
||||
<!-- upper bill: long, flat, with hooked tip -->
|
||||
<path d="M 564 316
|
||||
q 48 8 88 34
|
||||
q 10 7 8 13
|
||||
q -2 6 -12 4
|
||||
q -46 -10 -86 -34
|
||||
q -8 -6 -6 -12 q 2 -5 8 -5 z" fill="url(#beak)" stroke="#cf6a24" stroke-width="2.4"/>
|
||||
<!-- bill ridge highlight -->
|
||||
<path d="M 572 320 q 42 9 78 31" stroke="#ffd08a" stroke-width="2.6" fill="none" stroke-linecap="round"/>
|
||||
<!-- hooked nail at bill tip -->
|
||||
<path d="M 656 360 q 8 2 6 9 q -7 3 -12 -3 z" fill="#c25a1e"/>
|
||||
|
||||
<!-- Nostril slit -->
|
||||
<line x1="580" y1="324" x2="596" y2="330" stroke="#b85a1e" stroke-width="2" stroke-linecap="round"/>
|
||||
|
||||
<!-- ===== Near wing-arm reaching to handlebar ===== -->
|
||||
<path d="M 452 398
|
||||
q 40 -4 86 -22
|
||||
q 26 -10 52 -12
|
||||
l 14 6
|
||||
q -2 10 -14 12
|
||||
q -36 8 -64 24
|
||||
q -34 18 -64 16
|
||||
q -16 -8 -10 -24 z" fill="#efe8d8" stroke="#c2b59c" stroke-width="2.4"/>
|
||||
<!-- wing-hand gripping bar -->
|
||||
<ellipse cx="603" cy="370" rx="13" ry="9.6" transform="rotate(-24 603 370)" fill="#e6dcc6" stroke="#bfb29a" stroke-width="2.2"/>
|
||||
|
||||
<!-- ===== Near leg pedaling (down stroke) ===== -->
|
||||
<path d="M 432 444 q 4 36 24 58 q 4 10 -4 16" stroke="#f0973a" stroke-width="13" stroke-linecap="round" fill="none"/>
|
||||
<!-- pedal (near, down position) -->
|
||||
<g transform="translate(462 524)">
|
||||
<line x1="-16" y1="0" x2="16" y2="0" stroke="#3a3a3a" stroke-width="6"/>
|
||||
<!-- near webbed foot -->
|
||||
<path d="M -18 -3 l 30 0 q 14 1 18 10 l -8 3 l -8 -4 l -6 5 l -8 -4 l -7 4 l -11 -5 z" fill="#f0973a" stroke="#d8771f" stroke-width="2"/>
|
||||
</g>
|
||||
<!-- crank arms connecting pedals -->
|
||||
<line x1="455" y1="505" x2="462" y2="524" stroke="#3a3a3a" stroke-width="6" stroke-linecap="round"/>
|
||||
<line x1="455" y1="505" x2="449" y2="488" stroke="#3a3a3a" stroke-width="6" stroke-linecap="round"/>
|
||||
|
||||
<!-- Jaunty red neckerchief fluttering in sea breeze -->
|
||||
<path d="M 506 348 q 16 12 36 8 q -10 14 -28 12 q -14 -2 -18 -12 q 4 -7 10 -8 z" fill="#d94f3d"/>
|
||||
<path d="M 506 352 q -22 4 -38 -6 q 4 16 22 20 q 12 2 18 -4 z" fill="#c0392b"/>
|
||||
<path d="M 468 346 q -14 -2 -22 -12 q -2 12 8 18 q 8 4 14 -2 z" fill="#d94f3d"/>
|
||||
|
||||
<!-- breeze lines off the scarf and tail -->
|
||||
<g stroke="#cfe6ec" stroke-width="3" stroke-linecap="round" fill="none" opacity="0.8">
|
||||
<path d="M 430 350 q -18 -4 -30 6"/>
|
||||
<path d="M 350 376 q -20 -2 -32 8"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- subtle foreground vignette of warm light -->
|
||||
<rect width="960" height="640" fill="#ff9d3f" opacity="0.04"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 19 KiB |
Loaded 100 of 259 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user