Download js/engine.js from HelloSun/sfdemo: direct link, hf CLI and curl.
- Browser
- Download file 6.6 kB
-
https://huggingface.co/spaces/HelloSun/sfdemo/resolve/main/js/engine.js
- Command line
-
hf download hf://spaces/HelloSun/sfdemo/js/engine.js
-
curl -L -o engine.js https://huggingface.co/spaces/HelloSun/sfdemo/resolve/main/js/engine.js
6.6 kB
| // engine.js - Core constants & physics ported from Ikemen-GO (MIT, Suehiro & contributors) | |
| // Values mirror src/bytecode.go, src/char.go (CharVelocity/CharMovement init) and src/stage.go. | |
| // ---- State types (src/bytecode.go ST_*) ---- | |
| export const ST_S = 1, ST_C = 2, ST_A = 4, ST_L = 8, ST_N = 16, ST_U = 32; | |
| export const ST_MASK = 63; | |
| export const ST_SCA = ST_S | ST_C | ST_A; | |
| // Move types | |
| export const MT_I = 0, MT_U = 1, MT_H = 2; | |
| // ---- Attack types (src/bytecode.go AT_*) ---- | |
| export const AT_NA = 1 << 6, AT_NT = 1 << 7, AT_NP = 1 << 8; | |
| export const AT_SA = 1 << 9, AT_ST = 1 << 10, AT_SP = 1 << 11; | |
| export const AT_HA = 1 << 12, AT_HT = 1 << 13, AT_HP = 1 << 14; | |
| export const AT_AA = AT_NA | AT_SA | AT_HA; | |
| export const AT_AT = AT_NT | AT_ST | AT_HT; | |
| export const AT_AP = AT_NP | AT_SP | AT_HP; | |
| export const AT_ALL = AT_AA | AT_AT | AT_AP; | |
| // ---- Character flags ---- | |
| export const CF_HITSTOP = 1 << 0; | |
| export const CF_UNGUARDED = 1 << 1; | |
| export const CF_GUARDED = 1 << 2; | |
| export const CF_DIZZY = 1 << 3; | |
| export const CF_KO = 1 << 4; | |
| export const CF_JUGGLE = 1 << 5; | |
| // ---- Global constants ---- | |
| export const MaxPlayerNo = 6; | |
| export const GRAVITY_DEFAULT = 0.44; // cm.yaccel | |
| export const STAND_FRICTION = 0.85; // cm.stand.friction | |
| export const STAND_FRICTION_THRESHOLD = 2.0; | |
| export const CROUCH_FRICTION = 0.82; | |
| export const CROUCH_FRICTION_THRESHOLD = 0.0; | |
| export const DOWN_FRICTION_THRESHOLD = 0.05; | |
| // air.gethit (CharVelocity.init) | |
| export const AIR_GETHIT_GROUNDRECOVER = [-0.15, -3.5]; | |
| export const AIR_GETHIT_AIRRECOVER_MUL = [0.5, 0.2]; | |
| export const AIR_GETHIT_AIRRECOVER_ADD = [0.0, -4.5]; | |
| export const AIR_GETHIT_AIRRECOVER_BACK = -1.0; | |
| export const AIR_GETHIT_AIRRECOVER_FWD = 0.0; | |
| export const AIR_GETHIT_AIRRECOVER_UP = -2.0; | |
| export const AIR_GETHIT_AIRRECOVER_DOWN = 1.5; | |
| export const AIR_GETHIT_KO_ADD = [-2.5, -2]; | |
| export const AIR_GETHIT_KO_YMIN = -3; | |
| export const GROUND_GETHIT_KO_XMUL = 0.66; | |
| export const GROUND_GETHIT_KO_ADD = [-2.5, -2]; | |
| export const GROUND_GETHIT_KO_YMIN = -6; | |
| // movement (CharMovement.init) | |
| export const AIR_GETHIT_GROUNDRECOVER_GROUND_THRESHOLD = -20.0; | |
| export const AIR_GETHIT_GROUNDRECOVER_GROUNDLEVEL = 10.0; | |
| export const AIR_GETHIT_AIRRECOVER_THRESHOLD = -1.0; | |
| export const AIR_GETHIT_AIRRECOVER_YACCEL = 0.35; | |
| // stage defaults (newStage) | |
| export const STAGE_LEFTBOUND = -1000; | |
| export const STAGE_RIGHTBOUND = 1000; | |
| export const STAGE_SCREENLEFT = 15; | |
| export const STAGE_SCREENRIGHT = 15; | |
| export const STAGE_TOPBOUND = -60; | |
| export const STAGE_BOTBOUND = 80; | |
| // Canvas stage geometry (MUGEN-style local + camera) | |
| export const STAGE_LOCALWIDTH = 1280; | |
| export const STAGE_LOCALHEIGHT = 720; | |
| export const STAGE_FLOORY = 640; // where char.pos[1] === 0 sits in local coords | |
| export const STAGE_CAMERA_ZOOM = 1.0; | |
| export const ROUND_TIME = 99 * 60; // 99-second round timer (MUGEN default) | |
| export const MAX_JUGGLE = 8; | |
| export const MAX_GUARD_JUGGLE = 10; | |
| export const MAX_CHAIN = 10; | |
| export const clamp = (v, lo, hi) => v < lo ? lo : (v > hi ? hi : v); | |
| export const sign = v => v > 0 ? 1 : (v < 0 ? -1 : 0); | |
| export const abs = Math.abs; | |
| export const Btoi = v => (v > 0 ? Math.floor(v) : Math.ceil(v)); | |
| export const roundTo = Math.round; | |
| /** | |
| * Rect intersection test using MUGEN box semantics. | |
| * Box = [x1, y1, x2, y2] in character local space (y negative = up). | |
| */ | |
| export function boxOverlap(b1, b2) { | |
| return b1[0] < b2[2] && b1[2] > b2[0] && b1[1] < b2[3] && b1[3] > b2[1]; | |
| } | |
| export function rectWidth(b) { return b[2] - b[0]; } | |
| export function rectHeight(b) { return b[3] - b[1]; } | |
| /** | |
| * Physics integration ported from Ikemen-GO's char physics step. | |
| * Called once per active frame while the char is not paused. | |
| */ | |
| export function physicsStep(c) { | |
| const d = c.data; | |
| const air = c.pos[1] < 0 || c.stateType === ST_A; | |
| // horizontal velocity | |
| c.vel[0] += c.accel[0]; | |
| // friction (src/char.go uses stand/crouch friction w/ thresholds) | |
| const fric = c.stateType === ST_C ? CROUCH_FRICTION : STAND_FRICTION; | |
| const fricTh = c.stateType === ST_C ? CROUCH_FRICTION_THRESHOLD : STAND_FRICTION_THRESHOLD; | |
| if (Math.abs(c.vel[0]) > fricTh) { | |
| c.vel[0] -= sign(c.vel[0]) * fric; | |
| if (sign(c.vel[0]) !== sign(c.vel[0] - sign(c.vel[0]) * fric)) c.vel[0] = 0; | |
| } | |
| // vertical: yaccel only while airborne (MUGEN: vel[1] += yaccel down) | |
| if (air && !c.velFreezeY) { | |
| c.vel[1] += (c.yaccelOverride || d.yaccel); | |
| } | |
| // integrate | |
| c.pos[0] += c.vel[0]; | |
| c.pos[1] += c.vel[1]; | |
| c.pos[2] += c.vel[2]; | |
| // stage bounds | |
| if (c.pos[0] < d.leftbound) { c.pos[0] = d.leftbound; c.vel[0] = 0; } | |
| if (c.pos[0] > d.rightbound) { c.pos[0] = d.rightbound; c.vel[0] = 0; } | |
| if (c.pos[1] < d.topbound) { c.pos[1] = d.topbound; c.vel[1] = 0; } | |
| } | |
| /** Ground contact test: pos[1] === 0 in MUGEN space (y is negative up) */ | |
| export function onGround(c) { | |
| return c.pos[1] >= 0 && !c.airborne; | |
| } | |
| /** | |
| * Compute get-hit velocity, ported from Ikemen-GO hit velocity resolution. | |
| * Mirrors air.gethit.airrecover mul/add semantics. | |
| */ | |
| export function getHitVelocity(victim, attacker, hit) { | |
| const facing = attacker.pos[0] < victim.pos[0] ? 1 : -1; | |
| let vx = 0, vy = 0; | |
| if (victim.stateType === ST_A) { | |
| // airborne recovery | |
| if (hit.grounded) { | |
| vx = AIR_GETHIT_GROUNDRECOVER[0] * facing; | |
| vy = AIR_GETHIT_GROUNDRECOVER[1]; | |
| } else { | |
| vx = (AIR_GETHIT_AIRRECOVER_BACK * facing) * Math.abs(hit.vel[0]) * 0.5; | |
| vy = AIR_GETHIT_AIRRECOVER_UP; | |
| // juggle scaling: upward component decays with juggle count | |
| vy *= Math.max(0.35, 1 - victim.juggle * 0.075); | |
| if (victim.vel[1] > 0) vy += AIR_GETHIT_AIRRECOVER_DOWN * 0.4; | |
| } | |
| // apply hitdef velocity multiplier/add | |
| vx = (hit.vel[0] * facing) + vx * 0.5; | |
| vy = (hit.vel[1]) + vy * 0.5; | |
| } else { | |
| // grounded recovery: horizontal push scaled by xmul | |
| vx = (hit.vel[0] * facing) * GROUND_GETHIT_KO_XMUL; | |
| vy = hit.vel[1]; | |
| if (hit.guard) { | |
| // blocking: much weaker pushback | |
| vx = (hit.guard.vel[0] * facing); | |
| vy = hit.guard.vel[1]; | |
| } | |
| } | |
| return [vx, vy]; | |
| } | |
| /** Debug helper to render attr strings like Ikemen's attrString() */ | |
| export function attrString(attr) { | |
| if (!attr) return ''; | |
| let b = ''; | |
| if (attr & ST_S) b += 'S'; | |
| if (attr & ST_C) b += 'C'; | |
| if (attr & ST_A) b += 'A'; | |
| const pairs = [[AT_NA, 'NA'], [AT_NT, 'NT'], [AT_NP, 'NP'], | |
| [AT_SA, 'SA'], [AT_ST, 'ST'], [AT_SP, 'SP'], | |
| [AT_HA, 'HA'], [AT_HT, 'HT'], [AT_HP, 'HP']]; | |
| for (const [bit, label] of pairs) if (attr & bit) b += ', ' + label; | |
| return b; | |
| } |