Files
tilt/SPORTS_FRAMEWORK.md
T
ryanfitzpatrickio f9aa382f30 Merge goalie-tester worktree: modes, 3v3 goalies, OOB faceoffs, CF deploy.
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.
2026-08-03 10:41:13 -05:00

25 KiB
Raw Blame History

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)
  • hideCoveredBody coverage 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

  • applyIntent clamped 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 dont 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 isnt 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-tuned HIT constants
  • 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.js parameterized (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.js with 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 + capture screenshots
  • hitprobe for 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):

  1. Registry + data-driven catalogs (so new sports dont edit img2mesh.mjs internals)
  2. Side-by-side ref overlay in studio + CLI compare report
  3. Metric HUD (contact points, IK targets, capsule overlays — debug already half there)
  4. Drill API used by shootout machine, then tackle/shot drills for other packs
  5. Golden metric tests (blade height style) per sport pack
  6. 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/hockey vs stay single package with folders)

Phase 1 — Athlete + gear core (12 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 (12 weeks)

  • Extract ILocomotion from skaterSim
  • Keep skate model as implementation #1
  • Stub runPlant model good enough for a mannequin jog (even ugly)
  • Foot policy switch (glide vs plant) wired to animator

Phase 3 — Anim / IK platform (23 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 (23 weeks)

  • Impact profiles + rules callbacks
  • Generic play object + possession magnetism
  • Tool release sampling
  • Contested capture v1

Phase 5 — Arena + match shell (12 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:

  1. New athlete = skeleton + bodyStyle + loadout + loco profile + anim profile
  2. New equipment piece = sections/sockets (+ optional collider) + studio views
  3. New contact rule = impact profile + rules callback, no hit-system fork
  4. New ball sport = PlayObject + possession params + arena goals + AI facts
  5. New locomotion = one module implementing ILocomotion + foot policy
  6. New sport AI = roles writing intents/actions into the same seam as the pad
  7. Regression = headless metrics + shot sheets vs reference photos for that pack
  8. 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

  1. Keep hockey playable on main while extracting — no big-bang rewrite.
  2. Name the framework (e.g. still tilt core + sports/hockey, or promote Ludus-lineage body stack to a shared package both Ludus and tilt consume).
  3. Second pack choice determines Phase 24 emphasis:
    • Fighting → impact, reaction layers, stamina
    • Soccer → run plant, ball object, large arena
  4. Studio first among tools — equipment and poses improve only as fast as the ref-sheet loop; that loop is already the right shape.
  5. 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.