Spaces:
Running
Running
|
Download HANDOFF.md from HelloSun/flare-gamet2: direct link, hf CLI and curl.
- Browser
- Download file 16.1 kB
-
https://huggingface.co/spaces/HelloSun/flare-gamet2/resolve/main/HANDOFF.md
- Command line
-
hf download hf://spaces/HelloSun/flare-gamet2/HANDOFF.md
-
curl -L -o HANDOFF.md https://huggingface.co/spaces/HelloSun/flare-gamet2/resolve/main/HANDOFF.md
16.1 kB
| # HANDOFF — Flare 網頁版 (Flare web port) | |
| Read this first. It is written so another model/engineer can continue immediately. | |
| Project owner language: Traditional Chinese (replies to the user should be 繁體中文, short, factual). | |
| ## 1. What this is | |
| A pure HTML + JS (Canvas, no framework, no bundler at runtime) browser port of **Flare: Empyrean Campaign** | |
| (https://github.com/flareteam/flare-game + flare-engine), playable on a phone: touch joystick, tap-to-walk, | |
| auto-hunt (掛機), auto-target, tap skills/potions, bag/equipment/skills/teleport windows. UI loosely modelled on Lineage M. | |
| * Live: https://hellosun-flare-gamet2.static.hf.space/index.html (HF Space `HelloSun/flare-gamet2`, SDK static, public) | |
| * The user wants the port to match the ORIGINAL game's rules "as close to 100% as possible", verified, not guessed. | |
| * Original data used: `/root/app/flare-game` (af6eee6) and `/root/app/flare-engine` (b436611). Mods read in priority | |
| order: `empyrean_campaign` > `fantasycore` > engine `default` (tools/ini.py `MOD_ROOTS`). The engine C++ in | |
| `/root/app/flare-engine/src` is the ground truth for rules (PowerManager.cpp, Hazard*.cpp, EffectManager.cpp, Entity.cpp | |
| takeHit, LootManager.cpp, ItemManager.cpp, MenuPowers.cpp, StatBlock.cpp, Avatar.cpp, EntityBehavior.cpp). | |
| ## 2. User preferences / hard rules (violating these annoyed the user) | |
| 1. **NEVER delete remote files when uploading** (said twice). Upload with `HfApi.create_commit` + `CommitOperationAdd` | |
| only; never `delete_patterns`. Compare `list_repo_files` before/after and report `deleted: []`. | |
| 2. **"你自己要看,不要猜" — look at the real page, don't guess.** Reproduce with Playwright + screenshots, measure, then fix, | |
| then re-verify on the LIVE URL after upload (HF takes ~75–120 s to publish). | |
| 3. Memory guard: `/root/memkill.py` kills programs > 80 GiB RSS total (see AGENTS.md of the environment). Nothing here is close. | |
| 4. Answer concisely, no emojis, report limitations honestly. | |
| 5. HF auth: env `HF_TOKEN` is INVALID. Use `open('/root/.cache/huggingface/token').read().strip()` (account HelloSun). | |
| The user once pasted a token in chat (`hf_wfha…`): it is exposed; only read-only use was made; advise revoking. | |
| 6. Tooling gotchas: `pkill -f` kills your own shell → use `pkill -x -f "http.server 8082"`. Sandbox Chromium blocks | |
| HF Xet CDN images (PNA) → flags in tests/README.md for live tests. | |
| ## 3. Repo layout (`v1.zip` → `v1/source`, same tree as /root/app/flare-web) | |
| ``` | |
| index.html shell: HUD markup (#hud, #btnBag #btnSkill #btnTele, #skills, #fight ...), loads style.css + js/game.js | |
| style.css all CSS (HUD, bag/skill/shop windows .ov2 .winb .sk .tele, joystick hint) | |
| sw.js Service Worker template (placeholders __BUILD_ID__, __ASSET_LIST__ filled by inline_build.py) | |
| js/game.js THE GAME (~3300 lines, one IIFE) — see §5 | |
| data/world.json global data (items, powers, effects, tree, classes, statdef, xp_table, hero layers, npcs, spawnDefs ...) | |
| data/maps/*.json one file per map (tiles, collision, spawns, hotspots/portals, edefs = that map's monster defs) | |
| assets/ webp images: tilesets/, sprites/ (monsters), hero/ (equipment layers), fx/, loot/, npcs/, icons/, icons_items.webp | |
| tools/ data pipeline (python): build_data.py (main), build_items.py, build_powers.py, ini.py, inline_build.py | |
| tests/ Playwright verification scripts (+ README.md) | |
| README.md Space metadata (YAML header!) + user-facing notes. Keep the YAML front matter (sdk: static). | |
| HANDOFF.md this file | |
| ``` | |
| `dist` (what the Space serves) is produced by `tools/inline_build.py` into `/root/app/flare-dist`: `index.html` (CSS+JS+world.json | |
| inlined, ~1.4 MB), `sw.js`, `README.md`, `assets/`, `data/maps/`. `v1.zip` also contains `v1/dist/{index.html,sw.js,README.md}`. | |
| ## 4. Build / test / deploy workflow | |
| ``` | |
| cd /root/app/flare-web/tools | |
| python3 build_data.py # ~3 min: reads original Flare data -> data/world.json, data/maps/*.json, assets/ (webp) | |
| python3 inline_build.py # -> /root/app/flare-dist (inlines css/js/world.json, writes sw.js with BUILD id) | |
| cd /root/app/flare-dist && python3 -m http.server 8082 --bind 127.0.0.1 & # local test server | |
| python3 /root/app/flare-web/tests/v_engine.py # etc. (tests/README.md) | |
| ``` | |
| Only changed JS/CSS/HTML? `inline_build.py` alone is enough (no data rebuild). Changed anything under tools/ or original-data | |
| interpretation? run `build_data.py` first. After editing, always `node -e "new Function(require('fs').readFileSync('js/game.js','utf8'))"` | |
| as a syntax check. | |
| Deploy (add/overwrite only, never delete): | |
| ```python | |
| from huggingface_hub import HfApi, CommitOperationAdd | |
| api=HfApi(token=open('/root/.cache/huggingface/token').read().strip()); repo='HelloSun/flare-gamet2' | |
| before=set(api.list_repo_files(repo,repo_type='space')) | |
| ops=[CommitOperationAdd(path_in_repo=rel, path_or_fileobj=local) ...] # for a code-only change: index.html + sw.js ONLY | |
| api.create_commit(repo_id=repo, repo_type='space', operations=ops, commit_message='...') | |
| assert not (before - set(api.list_repo_files(repo,repo_type='space'))) | |
| ``` | |
| `index.html` and `sw.js` must always be uploaded together (build id inside). Static Space: no build step on HF. | |
| Then verify live: `curl -sL <url>/index.html | grep -c <new symbol>` loop, then run tests against the live URL. | |
| Service Worker: assets + `data/` cache-first (HF sends `no-store`; Cache Storage ignores it), `index.html` network-first (4 s | |
| timeout → cache). Install precaches only core assets (~12 MB); per-map tilesets/monsters and gear images are cached on first use. | |
| Cache name includes a build hash so any change busts old caches. | |
| ## 5. js/game.js map (grep the section banners `// ====` / `// ----`) | |
| | section (approx. line) | content | | |
| |---|---| | |
| | constants, utils, state `S` (≈9–60) | `HW/HH` iso tile size, `dirOf` (Flare direction numbering SW0 W1 NW2 N3 NE4 E5 SE6 S7), `dirKeep` hysteresis, `dirVec`; `S` global state | | |
| | loading / map runtime (≈61–185) | `getData` (inline FLARE_DATA or fetch), `preload`, `prepMap`, `ensureMap`/`loadMapAssets` (per-map lazy images, `releaseOldImages`) | | |
| | pathing (≈186–320) | A* 8-way no corner cutting, `smoothPath`, `routeTo`, `nearestEnemyPath`, `followPath` | | |
| | animation helpers (≈319–360) | `animFrame` (duration = whole animation; per-frame = duration/frames), `pickFacing` (mirror `(6-d) mod 8`), `setAnim` | | |
| | hero (≈359–460) | `statVal` (engine/stats.txt formula), `heroStats` (builds `H.st` table + derived fields), `newHero` (loads save, class start gear/powers) | | |
| | enemies (≈460–600) | `spawnOne` (rarity-weighted pick), `fillSpawns` (group chance), `killEnemy` (xp, loot), `updEnemy`, `onEnemyJoinCombat` | | |
| | ITEMS (≈653) | `RATES`, specEval/`makeExtendedItem` (Mastite randomizer), bag (`invAdd/invTake`), equip (`equipError`, flags, 2 equip sets, `enforceRequirements`), stat points (`heroAttrs`, `pointsLeft`, `spendPoint`), loot (`lootRoll`, `rollEnemyLoot`, `dropLoot`, pickup, `setLootGoal`), vendors/stock/prices | | |
| | POWER ENGINE (≈1068) | port of PowerManager/Hazard/EffectManager: `entStat`, `addEffect`, `effTick`, `initHazard`, `activatePower` → `powerFixed/Missile/Repeater/Spawn`, `hazardTick`, `hazDangerous` (animation active frame), `hitTarget` (= Entity::takeHit), `applyPostEffects`, `staggerOnHit` | | |
| | HERO POWERS (≈1520) | skill tree rules (`rankReqMet`, `idUnlocked`, `cellCur`, `cellNext`, `unlockCell`, `skillPointsLeft`), passives (`refreshPassives`), `barResolve`/`resolvePower` (rank + item bonus levels + ammo replace_power), `heroBegin`/`stepHeroPower` (attack animation, fires on active frame, charge) | | |
| | hero AI / creature AI | `autoFight`, `castSlot`, `doBasic`; `pickCreaturePower`, `startCreaturePower`, `stepCreaturePower`, `enemyTarget` | | |
| | SUMMONS (≈1802) | `powerSpawn`, `makeCreature`, `scaledDef` (stat_per_level), `updAlly`, spawn categories | | |
| | hero update (≈1921) | `updHero`: auto potions, loot goal walking, vendor walking, movement (joystick/path), pending skill, portals | | |
| | input/minimap/render/HUD/save | pointer handling (`onTap`: drops → npcs → enemies → ground), `render` (layers: tiles, floor hazards, entities depth-sorted, hazards, drops, texts), `updHud`, `buildSkillButtons`, `saveGame/loadSave` | | |
| | UI (≈2609) | `buildItemUI`: one overlay window with modes `inv` / `skill` / `tele` / `shop`, item/skill cards | | |
| | main loop (≈2983) | `frame`: variable dt for movement/AI, fixed 60 Hz `stepLogic` (effects, cooldowns, hazards) via accumulator; `separateEntities` | | |
| | `window.FlareWeb` (≈3086) | debug/test API (used by every test) | | |
| Save: `localStorage['flare-web-save-v1']`, `v:4` fields: pu (learned power ids), bar, alloc (stat points), cls/lvl/xp/hp/mp, gold, inv, eq/eqs/eqSet, | |
| ext (generated Mastite items), shop stock, map/x/y, flags. Old saves (v1–v3) are migrated in `newHero`. `flare-web-autopts` = auto-spend toggle. | |
| ## 6. Key design decisions (and why) | |
| * **Per-map lazy loading**: 50 maps, 113 MB of webp. Map JSON carries its own monster defs (`edefs`); images loaded on entry, | |
| old ones released. Hero equipment images load when worn (default body drawn until then). | |
| * **Direction numbering/mirroring**: frame lines are `frame=index,direction,x,y,w,h,ox,oy[,image]`. An early bug read index/direction | |
| swapped (hero "spinning"). `build_data.load_anim` is correct now; animation `duration` is the WHOLE animation. | |
| * **Logic rate**: engine rates are per 60 fps frame. Durations are exported in frames (`frames()` in build_powers.py) and speeds in | |
| tiles/s (`speed/60` per tick). Hazard "dangerous" frames depend on animation `active_frame`/`active_sub_frame` | |
| (arrows use `all`/`all`; default = (frames-1)/2 at sub-frame end). Missing this made fast arrows tunnel through enemies once. | |
| * **Items**: the "fantasycore" base item files only provide look; real items/ids/stats are in `empyrean_campaign/items`. | |
| `APPEND` files extend the lower-priority file of the same path (tools/ini.py `_read`). `equip_flags` last line wins; several `dmg` lines | |
| = several damage types; level-scaled values (`base:min:max:step,item_level:..,round:true,min:N`) are exported as specs and evaluated in JS. | |
| Randomizer items (Mastite, ids 432–494) are generated at drop time (`makeExtendedItem`, ids ≥ 100000, saved in `save.ext`). | |
| * **Loot**: one random pick per loot event (engine checkLoot: rarer tier wins, chance scaled by item_find, `loot_count`, fixed entries once, | |
| `requires_level`, `quantity_per_level`, `loot_drops_max`). With boosted drop rate gold is rolled independently so items don't crowd it out. | |
| * **Equipment rules**: requirements = level + every primary stat (stats include equipment bonuses); unmet equipped items go back to the bag; | |
| `requires_flags` of powers (shield, ranged, mental, bow/slingshot) gate skills; slingshot/bow swap powers via `replace_power`; | |
| two equipment sets (class `equipment_set=2,...`). | |
| * **Stat points**: engine rule 5/level, cap 75, manual `+` in the bag; auto-spend only when toggle `S.autoPts` is on (default OFF — | |
| user complained points "could not be added" because auto-hunt spent them instantly). | |
| * **Skills**: 3 tabs, 39 cells (36 skills +Sacrifice x3 in tabs, Sacrifice 238 excluded: needs quest status), 5 ranks, 2 points per level. | |
| Learned powers `H.pu` (Set of ids), action bar `H.bar` (6 slots of BASE ids, resolved to current rank at use). Class start powers come from | |
| engine/classes.txt `powers=`. | |
| * **Creatures**: spawn categories (`spawn_type` may be a category → weighted pick), `stat_per_level` scaling for summons, allies attack nearest enemy, | |
| enemies attack nearest of hero/allies, beacon calls neighbours, on_join_combat / on_half_dead / on_death powers. | |
| * **House rules (user request)**: `RATES = { xp: 100, drop: 10 }` in game.js — XP x100, item drop chance x10 (cap 100%, gold unchanged | |
| chance but independent). Set to `{1,1}` for the original game. | |
| * **Removed on purpose**: original monster damage x0.3 "balance" hack and every home-made formula (xp curve, hero stats, damage) were replaced | |
| by the engine's own. | |
| ## 7. Verification status (all pass at v1) | |
| See tests/README.md. Highlights: 738 spawn groups + 232 enemy defs, 502 items x17 fields, 332 powers x38 fields, 171 tree ranks, 38 effects, | |
| 49,560 equip decisions, 900 stat values, 9,736 loot cells, 4,212 combat distributions, 22 engine checks, 27,456 tree decisions, | |
| 4,539 Mastite distributions — all 0 mismatches. Auto-hunt 90 s per class: no deaths, no JS errors. (After the house-rate change the loot/xp | |
| scripts need `setRates(1,1)`.) | |
| ## 8. Known gaps (not original-identical) | |
| * No quests / story dialogs / quest XP; monsters respawn (original does not); Sacrifice passive unobtainable. | |
| * Some AI details (flee/chance_pursue etc.), map objects (chests, traps, teleport NPCs, events) not implemented; only the vendor NPCs. | |
| * No sound/music. No crafting, stash, buyback. | |
| * Hyperspace/iron_labyrinth maps excluded (dev hub / dead end); 50 of 56 maps exported (hyperspace used only to discover links). | |
| * High-level maps are original difficulty (a warning toast shows when a map is 3+ levels above the hero). | |
| * Real-device testing was never possible: everything verified in emulated mobile Chromium (touch events, 844x390 and 390x844). | |
| * `index.html` is ~1.4 MB because world.json is inlined (first load slower; cached afterwards). | |
| ## 9. Ideas / next steps (none requested yet) | |
| Quest statuses, chests, remaining enemy AI behaviours, audio, crafting/stash, performance work for very large maps (stormrock_pass 132x132, | |
| iron labyrinth excluded), a settings panel (RATES toggle UI), more PWA polish. | |
| ## 10. Development history (chronological, with commits on HelloSun/flare-gamet2) | |
| 1. Rewrote Flare as a mobile web game from original data: tools/build_data.py exports maps/tiles/monsters/skills; 6 maps, 3 classes | |
| (Brute/Scout/Adept); auto-lock, auto-hunt, tap-to-path, transparent joystick, skill/potion buttons, minimap, save. | |
| 2. Went to a pure-HTML single-file build (inline_build.py). That commit (`1a60e527`) removed js/, data/, tools/, old PNGs from the Space — the user | |
| later complained about deletions; since then NEVER delete. | |
| 3. Fixes driven by user reports: hero equipment layers + draw order per facing (`aa161545`); walking/attack spinning (`b502f836`); Service Worker cache | |
| (`79d37167`); monster wandering, joystick dead zone/smoothing (`ad2a684a`, `e6a5657f`); root cause of spinning = frame index/direction swapped in | |
| load_anim (`e3ebe185`); skill icons were cut at 32 px instead of 64 (`864352d1`); real skill effect animations (`9a464c7d`). | |
| 4. Item system (`929310a2`): drops, bag, equipment, vendor Abasi, icons, loot animations. Pickup fixes (`af664bf0`): tap a drop to walk and pick up, | |
| auto-hunt collects leftovers, no drops in portals. | |
| 5. 50 maps with lazy loading, visible portals, teleport menu, all vendors (`1ebb7bfb`). | |
| 6. "Make it identical to the original": spawn rules, monster stats/AI powers, XP table, hero formulas, combat resolution, loot counts, manual stat | |
| points (`9773240c`); items/equipment/flags/two sets (`3e8b55ac`). | |
| 7. Full power system: skill tree, hazard/effect engine, summons, Mastite random gear, scaled prices (`b4491c1e`). | |
| 8. House rates xp x100 / drops x10 (`ca362acd`); manual stat points by default + bigger + buttons (`63a5f47a`); `v1.zip` (`258723b1`). | |
| 9. This handoff + tests packaged into v1.zip (final upload of this session). | |
| Bugs worth remembering (root causes): monsters stacking on the hero (added `separateEntities`); portals bouncing the hero back (armed only after | |
| leaving a portal; never triggered during auto-hunt); drops spawning in portals; old saves (converted); Scout arrows invisible to hit test (active_sub_frame); | |
| dist map files not precached (data/ now cached cache-first); verification scripts first compared the wrong thing several times (e.g. paused game, stats | |
| before effects ticked) — when a parity test fails, first suspect the test hook (`setAlloc`, `learnAll` call `stepLogic` to apply passive effects). | |