Bring main menu (1v1/3v3), scrimmage with nets and goalies, dead-puck whistles at the nearest faceoff circle, skater board re-entry, and Cloudflare Workers/Pages deploy config. Keep main jersey gear stack and compact goalie floaters.
25 KiB
Sports Framework — Expansion Plan
What you already have is not a hockey demo with spare parts. It is a character + physics + intent stack that happens to be wired as ice hockey. The goal of this document is to name the seams honestly, list what still has to become sport-agnostic, and outline what would remain as sport packs so the same building blocks can ship football, basketball, soccer, fighting, racing-on-foot, and contact sports without forking the engine.
This is complementary to ROADMAP.md (hockey product depth) and README.md (current architecture). Here the question is:
What would it take for this package to be the framework under any sport game?
1. The thesis
A general sports framework is not “one locomotion model that does everything.” It is a small set of contracts plus pluggable sport modules:
| Layer | Owns | Sport-specific? |
|---|---|---|
| Body stack | skeleton, mesh, skinning, body style, ragdoll, proxy | mostly shared |
| Equipment stack | gear builders, sockets, coverage, colliders | shared builders; sport kits |
| Motion stack | pose buffer, crossfade, IK, override layers, clips later | shared runtime; sport poses |
| Locomotion | intent → velocity → footing | one model per surface/gait family |
| Impact | did hit / what kind / severity / handoff limp↔driven | shared core; sport rules |
| Object play | ball/puck possession, tool use, goals | sport packs |
| AI | roles, intent writers, formation | shared brain shell; sport tactics |
| Arena | bounds, surfaces, markings, static colliders | sport packs |
| Rules / match | clock, score, phases, sanctions | sport packs |
| Studio / harness | shot sheets, drills, capture, regression | shared tooling; sport catalogs |
Hockey already forces the hard problems (anisotropic friction, limb-level contact,
tool-in-hand IK, pure sim in shared/). That is why a framework can fall out of
this — but only if hockey stops being the type system.
2. What you already have (framework seeds)
These are reusable with little or no redesign.
2.1 Character body pipeline
- 23-bone A-pose skeleton (
skeleton.js) with regions, bone radii, segment graph - Procedural lofted body (
body.js) + distance-field skinning (skinning.js) - Body style morphs mass / muscle / fat (
bodyStyle.js) — already sport-neutral - Materials / team tints (
materials.js) — team identity, not hockey identity
2.2 Equipment geometry kit
gearMesh.js:loft,carvedShell,tube— sport-agnostic mesh grammar- Socket + skinned cloth pattern (hard shells on bones, cloth skinned with
aPart/aT) hideCoveredBodycoverage masks so kit replaces flesh
Hockey gear (skaterGear, goalieGear, stick) is content on top of that grammar.
2.3 Animation runtime
- Pose buffer + state crossfade + override layers (
skateAnimator.js) - Two-bone analytic IK (legs; goalie legs; stick grip reach partially)
- Override layers that multiply spine while replacing arms (stickwork) — correct pattern for “keep locomoting while acting”
- Animator does not own movement — sim/proxy own position (critical for all sports)
2.4 Physics character loop
- Proxy capsule for standing presence + kinematic ragdoll for pose/hits
- Limp handoff with root re-expression so get-up does not double-apply slide
- Limb-pair hit classification from posed capsules (
hits.js) — the right separation of did hit vs what hit - Collision layers / tags (
bridge.js) — extensible KIND system
2.5 Intent seam
applyIntentclamped entry (skaterSim.js) — controller, AI, and netcode all write the same shape- Camera-relative stick (
input.js) — gamepad/keyboard already abstracted
2.6 Pure sim island
shared/free of three.js, headless-testable, server-ready- Headless test culture (pose, AI, physics, hits) + capture / img2mesh tools
2.7 Studio / reference loop (seed)
- Character studio (
character.html+img2mesh) with pose presets, fixed views, shot sheets, optional live drill (puck machine) - Pattern: author → capture sheet → drop refs → re-capture → diff by eye (or later pixel)
3. What is still hockey-shaped (must peel off)
These block “any sport” until they become interfaces + plugins.
| Hardcoded today | Why it blocks generality |
|---|---|
shared/skaterSim.js is the locomotion |
Running, plant-and-cut, swim, bike, skate all need different footing models |
shared/rink.js is the arena |
Rect + corner radius is not a pitch, court, ring, or track |
shared/ai.js puck roles + rink half |
Roles are puck/carrier/chaser; no ball/goal/zone abstraction |
possession.js + stick blade |
Magnetism dial is excellent, but assume blade + cylinder puck |
stick.js grip-space tool |
Need generic held tool / ball socket / two-hand implement |
hits.js CAN_DELIVER / ice thresholds |
Sport contact rules differ (tackle vs check vs foul vs strike) |
goalie as special entity type |
“Keeper” should be a role/locomotion profile, not only a hockey class |
match.js / shootout.js |
Match loop is sport session; not a generic phase machine |
input.js Skill Stick / hockey stop |
Sport action maps differ; need bindable action sets |
| Pose catalogs only skate / stick / goalie | Need gait families + sport action libraries |
| Physics world assumes ice plane + boards | Surfaces (friction, bounce), goals, posts, glass, hoops, cages |
| Package name / scripts assume hockey MVP | Cosmetics; real issue is import graph coupling |
4. Target architecture
packages/ (or src/ layered)
core/ math, rng, time, determinism helpers
body/ skeleton, body loft, skinning, bodyStyle, ragdoll, proxy
gear/ loft/shell/tube builders, socket attach, coverage
anim/ pose buffer, blend, IK solvers, layer stack, clip hooks
locomotion/ interface + models: skate, run, plant, crouch-shuffle, …
impact/ contact resolution, limb pairs, severity curves, handoff
object/ free bodies, attachment/magnetism, tools, projectiles
arena/ surfaces, bounds, static colliders, markings API
brain/ intent writer, roles, blackboard, formation helpers
match/ phase machine, clock, score, events (sport-agnostic shell)
input/ device → action set → intent
render/ materials, camera presets, arena mesh helpers
studio/ pose/view catalogs, capture API, ref sheets, drills
harness/ headless scenarios, golden metrics, capture regression
sports/
hockey/ rink, skate loco, stick, puck, check rules, shootout, AI
(future packs)
football/
basketball/
soccer/
fighting/ (Ludus-adjacent — already half-solved)
…
Rule: a sport pack may depend on the framework. The framework must not import a sport pack.
Hockey becomes the first pack that proves the contracts. Ludus fighting is the second pack if contact + minigame reuse is the priority (see ROADMAP fighting note).
5. Contracts to extract (the real work)
5.1 Locomotion model (ILocomotion)
Have: carve/glide blade model + intent fields (ix, iz, sprint, brake).
Need:
createState(spawn, profile) → state
applyIntent(state, msg) // clamped, never trusts client
step(state, dt, env) // env: surface, contacts, clamps
readout(state) → { speed, gaitPhase, bank, plantL, plantR, … }
Locomotion families to support eventually:
| Family | Examples | Key physics |
|---|---|---|
| Blade / edge | ice hockey, figure, speed skate | anisotropic friction, carve |
| Run plant | soccer, basketball, football | foot plant, cut friction, acceleration |
| Crouch / shuffle | goalies, catchers, linemen | lateral step without full gait |
| Contact-limited | rugby ruck, board pin | reduced mobility under constraint |
| Aerial / jump | basketball, volleyball, headers | ballistic + land recovery |
| Vehicle-assisted | cycling, wheelchair, luge | separate later; not v1 |
Left to build:
- Shared
env(surface µ, max slope, out-of-bounds clamp hooks) - Foot plant bookkeeping for run gaits (world-planted feet vs mover-local skate feet)
- Profile data (top speed, accel, turn radius by sport / position / fatigue)
- Unit tests per model mirroring
test/skaterSim.mjs
5.2 Footing & IK policy
Have: mover-local foot targets for skating; world-height leg IK for goalie pads.
Need:
IFootPolicy: glide | plant | pivot | kick | slide- World-space plant IK for walk/run (Ludus-style), without regressing skate glide
- Arm IK generic: two-hand implement, one-hand hold, catch, block
- Spine/look-at IK for tracking ball / opponent (broadcast + AI readability)
- Hand-to-object solve that reports reach error (you already see this on stick grip)
Left to build:
- Unified IK module (two-bone + simple FABRIK/pole vectors), shared by sports
- Constraint priorities (feet > implement > look) so layers don’t fight
- Authoring helpers: target sockets in mover-local vs world
5.3 Pose / animation system generalization
Have: states + stick override layers + goalie stance set.
Need:
- Locomotion graph (idle, walk, run, sprint, cut, stop, jump, land, fall, getup)
- Action layers (shoot, pass, tackle, catch, block, celebrate) with blend masks
- Clip adapter interface early — procedural remains default; mocap/clips plug in without rewriting sports (ROADMAP already names motion matching as the ceiling)
- Shared get-up / knockdown / stagger presentation states (physics-driven entry)
Left to build:
- Pose catalog format (JSON or pure JS) keyed by sport + role
- Layer stack API (replace vs multiply vs additive per bone set)
- Phase drivers from locomotion readout (gait phase, effort, bank)
- Optional motion-match slot (even if empty) so content pipeline isn’t a rewrite later
5.4 Equipment framework
Have: builders + hockey kits + stick as special tool.
Need:
EquipmentDef {
id, sockets[], skinnedParts[], rigidParts[],
colliders[], coverage[], massAdd, tags[]
}
ToolDef extends Equipment // stick, bat, racket, gloves-as-weapons
WearableDef extends Equipment // helmet, pads, jersey, cleats
Left to build:
- Data-driven attach (bone name + local TRS + optional skin weights)
- Size from physique (you already scale gear off
physique— generalize) - Collider generation from mesh bounds or authored capsules
- Damage/armor hooks by
REGION(skeleton already has regions “from GDD”) - Asset path: procedural first, imported glTF meshes later behind same sockets
- Reference-driven iteration: shot sheet per equipment part (front/side/3q/detail)
5.5 Player assembly (createAthlete)
Have: createSkater, createGoalie — two assembly paths.
Need: one factory:
createAthlete({
seed, bodyStyle, team,
locomotion: 'skate' | 'run' | …,
role: 'skater' | 'keeper' | 'striker' | …,
loadout: EquipmentDef[],
animProfile,
impactProfile,
})
Roles swap locomotion, gear, AI profile, and action map — not hard forks of the character.
5.6 Impact / contact sports core
Have: proxy collision + limb pair + severity tiers + limp handoff + get-up fix.
Need:
- Impact profiles (thresholds, limbShare, bonuses, legal deliverers) per sport
- Legal / illegal contact classification as rules callback, not hardcoded hockey
- Multi-body pile stability (proxy equilibrium already soft under pressure)
- Grapple / pin / clinch states (football tackle finish, wrestling, board battle)
- Strike profiles for combat sports (Ludus) reusing limb pairs + reaction curve
- Surface hits (wall, boards, post) feeding same severity pipeline
- Injury / fatigue hooks off region + severity (data only; presentation later)
Left to build:
createImpactSystem({ profile, rules })replacing hockey-tunedHITconstants- Deliverer allow-lists as data
- Contested body state (pinned, held, blocked) separate from limp
- Harness scenarios: open ice hit, wall pin, tackle wrap, pile-up
5.7 Object / ball / tool play
Have: puck body, blade collider, magnetism possession dial, release step-clear.
Need:
- Generic
PlayObject: sphere / cylinder / prolate (ball types), mass, restitution - Attachment models: hard parent, soft magnetism, carry socket, two-hand cradle
- Release models: impulse from tool velocity, throw arc, kick from foot bone
- Contested possession (you already noted nearest-index-wins is too coarse)
- Multi-object (only one puck today)
Left to build:
object/possession.jsparameterized (radius, catch cone, break force, magnetism)- Tool velocity sampling at release (stick already aims; generalize to foot/hand)
- Interaction matrix: body part × object × surface
- Sport rules for out-of-bounds / last touch (thin event layer)
5.8 Arena / surface system
Have: NHL rink numbers, board outline, ice clamp, markings texture, nets.
Need:
ArenaDef {
bounds, // polygon or compound
surfaces[], // friction, restitution, name
staticColliders[],
goals[] | targets[],
spawns[],
markings, // render only
}
Left to build:
- Containment for rect, rounded-rect, circle, custom polyline
- Surface queries under feet (ice vs turf vs hardwood changes loco)
- Goal/score volumes as triggers (net is hockey-specific geometry)
- Camera volumes / broadcast rails optional metadata
5.9 AI brain shell
Have: waypoint steer, personal space, board lookahead, carry/chase/support/defend via nearest-to-puck, intent-only outputs.
Need:
Brain {
sense(world) → facts
rolePolicy(facts) → role
tactic(role, facts) → goal
motor(goal) → intent + actions
}
Left to build:
- Blackboard / facts schema (ball, goals, teammates, opponents, clock, score)
- Role set as data per sport (not four hockey enums only)
- Formation / spacing utilities (slots relative to ball and own goal)
- Difficulty as parameter on reaction lag, aim cone, decision rate
- Keeper brain as role using shuffle locomotion (generalize goalie)
- Headless scenario tests: “defend lead”, “press high”, “box out” per pack
Hockey AI depth (forecheck systems, etc.) stays in the hockey pack / ROADMAP.
5.10 Match / rules shell
Have: createMatch loop, shootout phase machine, goal detection helpers.
Need:
- Generic phase machine: warmup → live → stoppage → restart → end
- Event bus: goal, foul, out, period, possession change, hit
- Score / clock / period as configurable
- Restart spawns from arena + rules (faceoff, kickoff, freethrow, scrum)
Left to build:
match/runtime.jswith sport rules plugin- Deterministic event log (gates replay + netcode later)
- Minimal HUD adapter (score/clock) sport-agnostic
5.11 Input / action maps
Have: Xbox + keyboard, Skill Stick gestures, rumble on hit.
Need:
- Action set per sport (
move,sprint,primary,secondary,skillAxis, …) - Gesture recognizers as plugins (Skill Stick shoot is one plugin)
- Rebind + dual-input last-wins (keep current behavior)
5.12 Studio, reference sheets, training harness
This is the differentiator you called out. Today it is hockey-shaped but the loop is correct.
Have:
- Live studio with pose/view catalogs
- CLI shot sheet (
img2mesh) - Puck machine drill for goalie
- Headless tests +
capturescreenshots hitprobefor contact
Need for framework-grade training harness:
| Capability | Purpose |
|---|---|
| Subject registry | any athlete role + loadout, not only player/goalie |
| Pose/view catalogs as data | sport packs register entries |
| Reference slots | ref/<sport>/<subject>/<pose>_<view>.png side-by-side |
| Diff mode | flip / onion-skin / histogram vs previous capture |
| Drill factory | spawn scenario (1v1, shooting gallery, tackle pad, PK) |
| Metric probes | blade height, foot plant error, reach error, joint limits, timing |
| Scenario packs | headless scripts asserting metrics, not only “no NaN” |
| Optional photo-guided authoring | import ref image as camera-aligned overlay (not auto-mesh) |
| Later: mesh-from-image assist | silhouette / part labels → loft section hints (research; not blocking) |
Important scope note: true image → production mesh (full photogrammetry / generative retopo) is a product of its own. The framework should treat reference photos and sheets as the iteration contract, with procedural gear remaining the authoritative runtime asset until a mesh pipeline earns its keep.
Left to build (practical order):
- Registry + data-driven catalogs (so new sports don’t edit
img2mesh.mjsinternals) - Side-by-side ref overlay in studio + CLI compare report
- Metric HUD (contact points, IK targets, capsule overlays — debug already half there)
- Drill API used by shootout machine, then tackle/shot drills for other packs
- Golden metric tests (blade height style) per sport pack
- Optional: capture matrix in CI on PR (you already have the capture tool)
6. Sport packs — what each still needs on top of the framework
Once contracts exist, each sport is mostly content + rules + AI policy, not a new engine.
6.1 Ice hockey (first pack — mostly present)
Done enough: skate loco, rink, stick, puck, checks, shootout, basic AI, goalie MVP.
Pack backlog: see ROADMAP (full 5v5 rules, goalie depth, positional AI, possession feel).
6.2 Fighting / combat (highest reuse from Ludus + impact core)
- Strike / block / grab action layers
- Ring/cage arena
- Stamina and limb disable via
REGION - Existing fighting system port (ROADMAP already flags this)
6.3 Soccer
- Run plant locomotion + ball foot/chest/head contacts
- Large pitch arena, goals, out/throw-in/corner rules
- No (or soft) limb impact; shoulder challenge profile
- AI: formations, offside line fact, keeper dive (shuffle + dive loco)
6.4 Basketball
- Run + jump + plant, ball carry/dribble magnetism variant
- Court, hoop trigger, shot arc from release
- Screen / box-out contact (low severity impact profile)
- AI: pick-and-roll roles, spacing
6.5 American football / rugby
- Run + explosive cut; tackle as grapple-to-limp pipeline
- Field, downs/ruck rules plugin
- Heavy impact profile; pile-up stability critical
- AI: playbooks as scripted formations + read trees
6.6 Basketball-adjacent / volleyball / handball
- Shared run + jump + catch/throw object model
- Different arenas and score targets
6.7 Non-goals for v1 framework
- Full vehicle sports, water sports, golf swing fidelity, licensed broadcast packages
- Complete mocap library (architecture must allow it; content is separate)
7. Gap list — prioritized build order
Phased so each phase ships a usable contract and keeps hockey working as the proving pack (strangler pattern: extract behind interfaces, re-home hockey).
Phase 0 — Inventory & seams (days)
- Draw import graph: mark
shared/*vs sport-coupled modules - Freeze contracts in a short
docs/contracts.md(or this file §5 as source) - Prove Box3D determinism (ROADMAP) — gates multiplayer and harness replay
- Decide monorepo layout (
sports/hockeyvs stay single package with folders)
Phase 1 — Athlete + gear core (1–2 weeks)
createAthlete+ loadout list; skater/goalie become configs- EquipmentDef schema; move stick/pads to data-ish modules
- Physique-driven sizing API documented
- Studio subject registry reads loadouts
Phase 2 — Locomotion interface (1–2 weeks)
- Extract
ILocomotionfromskaterSim - Keep skate model as implementation #1
- Stub
runPlantmodel good enough for a mannequin jog (even ugly) - Foot policy switch (glide vs plant) wired to animator
Phase 3 — Anim / IK platform (2–3 weeks)
- Shared IK solvers module
- Layer stack API (locomotion / action / reaction / getup)
- Reaction/stagger as layers driven by impact system
- Clip adapter interface (null implementation OK)
Phase 4 — Impact + object cores (2–3 weeks)
- Impact profiles + rules callbacks
- Generic play object + possession magnetism
- Tool release sampling
- Contested capture v1
Phase 5 — Arena + match shell (1–2 weeks)
- ArenaDef; rink becomes hockey arena pack
- Phase machine + event log
- Score/clock/HUD adapters
Phase 6 — Brain shell (2+ weeks, ongoing content)
- Facts blackboard + role policy
- Formation helpers
- Keeper role using shared pieces
- Scenario harness for AI regressions
Phase 7 — Studio / training harness productize (parallel after Phase 1)
- Data-driven pose/view catalogs
- Reference overlay + capture matrix
- Metric probes + golden tests
- Drill factory (shooting, tackling, 1v1)
- Optional ref-image camera plate
Phase 8 — Second sport pack (proof)
Pick one of:
- Fighting (max reuse of impact + Ludus), or
- Soccer prototype (max pressure on run loco + ball object)
Success criterion: second sport does not fork skeleton, ragdoll, gearMesh,
hits core, or studio capture — only pack content + profiles.
8. What “done” looks like for the framework (not for a sport)
You can claim a sports framework when all of these are true:
- New athlete = skeleton + bodyStyle + loadout + loco profile + anim profile
- New equipment piece = sections/sockets (+ optional collider) + studio views
- New contact rule = impact profile + rules callback, no hit-system fork
- New ball sport = PlayObject + possession params + arena goals + AI facts
- New locomotion = one module implementing
ILocomotion+ foot policy - New sport AI = roles writing intents/actions into the same seam as the pad
- Regression = headless metrics + shot sheets vs reference photos for that pack
- Hockey still runs as a pack on top of the same cores
Not required for “framework done”: full NHL parity, licensing, netcode, franchise modes (those are product; see ROADMAP).
9. Risks & non-goals
| Risk | Mitigation |
|---|---|
| Over-abstract before second sport | Extract only when hockey + one other pack demand the seam |
| Run gait looks like skating forever | Foot plant policy is a hard gate for sport #2 |
| Proxy pile-ups | Dedicated multi-body scenarios before rugby/football |
| Mocap FOMO | Keep procedural + layer API; add clips when content exists |
| img2mesh becomes “AI will make the gear” | Refs guide humans/tools; runtime assets stay authored procedural/glTF |
| Determinism ignored | Prove early; harness replays depend on it |
God-object match.js |
Event log + rules plugin from the start of Phase 5 |
Non-goals: replacing Unity/Unreal; full photogrammetry pipeline; every Olympic sport in year one; network-complete framework before single-player packs feel good.
10. Suggested near-term decisions
- Keep hockey playable on main while extracting — no big-bang rewrite.
- Name the framework (e.g. still
tiltcore +sports/hockey, or promote Ludus-lineage body stack to a shared package both Ludus and tilt consume). - Second pack choice determines Phase 2–4 emphasis:
- Fighting → impact, reaction layers, stamina
- Soccer → run plant, ball object, large arena
- Studio first among tools — equipment and poses improve only as fast as the ref-sheet loop; that loop is already the right shape.
- Do not wait for mocap to generalize animation runtime; wait for mocap to fill it.
11. One-page checklist — “left to expand”
Core extractions
- Locomotion interface + run/plant model
- Foot / arm / look IK module
- Anim layer stack + clip adapter
- Athlete factory + data loadouts
- EquipmentDef / ToolDef schema
- Impact profiles + rules hooks
- PlayObject + generic possession
- ArenaDef + surfaces
- Brain blackboard + roles
- Match phase machine + events
- Action-map input
Harness / content pipeline
- Sport-registered studio catalogs
- Reference photo overlay + capture matrix
- Metric probes + golden pose/contact tests
- Drill factory for training scenarios
Proof
- Hockey re-homed as pack
- Second sport pack without core forks
- Determinism + replay of harness scenarios
Explicitly still sport content (never “framework only”)
- Poses, gear shapes, AI tactics, rules edge cases, audio, broadcast, modes, licensing
12. Bottom line
You are closer than a greenfield sports engine because the hard abstractions are already proven in anger:
- intent-driven locomotion
- animator subordinate to physics
- limb-level contact from posed ragdolls
- limp ↔ driven handoff without teleports
- tool-in-hand dependency order
- possession as a dial
- pure sim + headless tests + reference shot sheets
What remains is not “invent sports tech.” It is peeling hockey off the type system, turning each proven spike into a contract + pack content, and making the studio/harness the way every new sport earns visual and mechanical correctness from reference photos and scenario metrics.
Hockey ROADMAP remains the depth plan for pack #1. This document is the breadth plan for the chassis underneath packs #1…N.