Spaces:
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
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> enginedefault(tools/ini.pyMOD_ROOTS). The engine C++ in/root/app/flare-engine/srcis 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)
- NEVER delete remote files when uploading (said twice). Upload with
HfApi.create_commit+CommitOperationAddonly; neverdelete_patterns. Comparelist_repo_filesbefore/after and reportdeleted: []. - "你自己要看,不要猜" — 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).
- Memory guard:
/root/memkill.pykills programs > 80 GiB RSS total (see AGENTS.md of the environment). Nothing here is close. - Answer concisely, no emojis, report limitations honestly.
- HF auth: env
HF_TOKENis INVALID. Useopen('/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. - Tooling gotchas:
pkill -fkills your own shell → usepkill -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):
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_animis correct now; animationdurationis 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/60per tick). Hazard "dangerous" frames depend on animationactive_frame/active_sub_frame(arrows useall/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.APPENDfiles extend the lower-priority file of the same path (tools/ini.py_read).equip_flagslast line wins; severaldmglines = 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 insave.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_flagsof powers (shield, ranged, mental, bow/slingshot) gate skills; slingshot/bow swap powers viareplace_power; two equipment sets (classequipment_set=2,...). - Stat points: engine rule 5/level, cap 75, manual
+in the bag; auto-spend only when toggleS.autoPtsis 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 barH.bar(6 slots of BASE ids, resolved to current rank at use). Class start powers come from engine/classes.txtpowers=. - Creatures: spawn categories (
spawn_typemay be a category → weighted pick),stat_per_levelscaling 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.htmlis ~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)
- 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.
- 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. - 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). - 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. - 50 maps with lazy loading, visible portals, teleport menu, all vendors (
1ebb7bfb). - "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). - Full power system: skill tree, hazard/effect engine, summons, Mastite random gear, scaled prices (
b4491c1e). - House rates xp x100 / drops x10 (
ca362acd); manual stat points by default + bigger + buttons (63a5f47a);v1.zip(258723b1). - 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).