Spaces:
Running
Running
Commit History
-
chore(data): regenerate CPU demo with polish-r4 rules
The shipped set now carries shot_02's pot (11 flags, none lost) and drops shot_07's false track -- 54 of its 56 points survive the cue exclusion and peak 1.03 cm from the seed against MOVE_MIN_M 0.06, so no ball that moved lost a track. Every leaf outside ball_tracks reproduces on all 14 against both ad534bb8 and r3; inside it only shot_07, the one shot with a cue hole over LOST_MAX_S (+1 point).ba200bf -
fix(fe): suppress cushion-snap when a ball sits at the apex
A track turns round off a rail or off a BALL and near a cushion the two look alike: shot_02's cue turns 152 degrees 10.3 cm off the far rail with the 5 sitting 6.9 cm away. collisions calls that kink and shot_05's real bounce `unknown` alike, so the veto reads the layout in both places the picture holds it. shot_02 1 snap -> 0 (50 canvas pixels, all in the removed segment); shot_05 canvas byte-identical.1e61cc4 -
feat(fe): dashed cushion-snap for near-rail turnarounds
A ball pressed against a cushion is half hidden by it, so its bbox centre reads ~6.5 cm short and the drawn line turns round in mid-air -- shot_05 apexes at y 2.4464 with the rail at 2.54. The measurement cannot move (the fix is full boxes on the ND1 line), so the DRAWING closes the gap: an apex within CUSHION_SNAP_M 0.12 of its nearest rail that turns at least 90 degrees gets a dashed segment out to that rail, the same language the pocket-bound stretch uses. JSON unchanged. Accepted on the DOM: shot_05 ends on the rail pixel row, shot_01 and shot_03 grow nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>4f0b4f3 -
chore(data): regenerate CPU demo with hybrid far-zone detect
The detector changed, so the shipped demo had to come from the pipeline that now exists. First regeneration whose gate is not "nothing may change": boundaries 14/14, no pocket flag lost and the one new flag only on shot_10 (10 total, eye-checked), suspect flagged 0/14, ball 5 present at conf 0.688, shot_05 coverage 1.000, rack_guess on 09+10 only. Reproduces the first run leaf for leaf outside timings and rack_guess.46857ab -
feat(fe): draw ghost rack when rack_guess is on
The break frame comes back with four or five of nine balls -- the rest are physically hidden behind them at this camera angle. The JSON keeps measurements plus one flag, so the missing balls belong to the drawing: a dashed, faint diamond under the balls that were really seen. Gate: 3 contract tests; toDataURL diff shows all 9 ghosts plus the caption and leaves every measured ball pixel untouched.3d87b03 -
feat(analyzer): rack_guess display flag for occluded break racks
Down the table the rack is a diamond seen edge on, so balls hide behind balls and 9 of 9 is unreachable -- at 1280 the shot_09 cluster even returns ten boxes for nine balls. Flag it instead of inventing points: 3+ balls inside 0.15 m of one centre with the cue 0.5 m away. Gate: on for shot_09 (4 of 9 seen) and shot_10 (5 of 9), off for shot_02/03/05; 9 unit tests; pytest 2x green on both venvs.2bb6493 -
feat(analyzer): load ball-set colour profile for BallID
The tournament uses Dynaspheres Titan (the 5 is purple, not orange), so a correctly detected ball still gets the wrong number. load_profile reads a JSON of palette / white_range / the ball-8 thresholds; the method's own constants stay unreachable from it. With POOLCOACH_BALLID_PROFILE unset nothing moves: no key in the job JSON, the default palette, and the 14 demo shots still serialise without one. pytest green 3x in both venvs.d5faecf
-
chore(data): regenerate the CPU demo bundle with pocket-bound flags
The shipped flags came from the near-pocket rule alone. Same VOD and corners; this round leaves the tracker alone, so the comparison walked into ball_tracks too: 0/84 headline cells, 0 other pre-existing leaves down to every track point, 0 shot boundaries, all 14 overlays byte for byte identical. pocketed_guess 7 -> 9 (shot_03 and shot_14); tracks, points, steal-ends and longest hole all unchanged.b33d4a2 -
feat(fe): extrapolate pocket-bound balls into the pocket on replay
Measuring stops where the detector gave up, so a pocket-bound ball stalled on the cloth. The replay now rolls it the last stretch at the speed it was last seen doing, and the table view draws that piece DASHED so measured and inferred never look alike. Drawing only -- nothing is written back. Gate: shot_03 replay ends 0 cm from the pocket against 23.75 cm short before; pytest 213 app / 227 cv.36d3768 -
feat(api): expose the guessed pocket in ball tracks
The pocket-bound guess is useless to the drawing without the pocket it guessed, so the result schema carries it: an optional pair of coordinates, absent when there is no guess and absent from every result stored before it existed. It only ADDS -- no number above it moves. Gate: pytest 210 app-env / 224 cv-env, and a track built without a pocket still validates and dumps no phantom key.16d608d -
chore(data): regenerate the CPU demo bundle with capped ball tracks
The shipped tracks came from the uncapped gate. Same VOD and corners; only ball_tracks was allowed to move, and nothing else did: 0/84 headline cells, 0 other pre-existing leaves, 0 shot boundaries. Tracks 19 -> 17, pocketed_guess 1 -> 7, ends within 3 cm of another ball 6 -> 3, longest hole in a track 5.267s -> 0.467s.e837cee -
chore(data): regenerate the CPU demo bundle with ball tracks
The shipped demo still showed only the cue moving. Same VOD and corners as the 25/08 bundle; object-ball tracking may only ADD, so the gate was that nothing else moved: 0/84 headline cells, 0 leaves over a FULL comparison of every pre-existing key, 0 shot boundaries. Adds 19 ball_tracks over 14 shots, 1 flagged pocketed.0abf5e0 -
feat(fe): draw and replay object-ball tracks on the table view
The table view showed only the cue moving while the JSON already carried every ball. Tracks are resampled onto one even time base before animateShot, which interpolates by index -- untouched, so the collisions still land where they landed. A numberless ball is grey, not a second white cue. Gate: pytest 194/208 green; verified live (paths at 0.6 alpha, potted fade, no ghost ball: 145 vs 279 px).b0d16f3 -
feat(api): expose ball_tracks in the analyze result schema
The per-shot JSON gains the object-ball paths. Default empty so every result stored before tracking existed still validates and the demo bundle does not break; nothing above it changes meaning. Gate: pytest 172/186 green, 3 new schema contract tests.6903d96
-
fix(fe): shorten the cpu warning note
Four sentences of caveat before the first shot list; two carry the same information (how slow, what fills in, that a sample is already there). No test locked the old wording, so no gate key changed.a0fcbb6 -
feat(analyzer): rebaseline the demo bundle on the cpu pipeline
The shipped set came from a CUDA worker, which does not reproduce (131 cells differed between two runs, 13-14/08). CPU does, so the Space now ships what its own hardware produces. Same VOD and corners, run 25/08 at 4c84e05: 14/14 shots equal to column C of the 24/08 three-way check on every field, 14/14 shot boundaries equal to the 14/08 manifest. Last Vietnamese strings gone -- regenerated, never hand-edited.1c0d153 -
chore(repo): normalize line endings with text=auto
The tree mixed CRLF and LF with only *.bat declared, so an English-only edit produced a 3704-line diff and every commit needed its EOL restored by hand. Content is untouched: git diff --ignore-cr-at-eol shows .gitattributes alone. pytest 160+7skip (app) / 167 (cv), 3 runs each.2cfc662
-
fix(fe): scope the viz caption width cap to the ball column
`max-width: 120px` sat on `.viz-cap` itself, so the caption in the wide right-hand column was squeezed too and broke one sentence over five cramped lines. The cap now belongs to the ball column alone, and a non-breaking space keeps "0.37 down" from splitting. Gate: pytest 160 passed + 7 skipped, 2 runs; label 120px/5 lines -> 250px/2, hero 226px -> 181px tall, 258 -> 215 at 375px, no overflow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>faf3aa9 -
fix(fe): reword hero captions in viewer-facing words
A dot at the top right captioned a=-0.20 reads backwards to anyone watching the ball, so the caption now says "0.20 right, 0.27 up" through one contactWords the quickbar shares. The convention is unchanged and the signed pair stays in the numbers table, which also takes the checkpoint name the viewer-facing label no longer carries. Gate: pytest 160 passed + 7 skipped, 2 runs; 167 collected, unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>da71d35 -
fix(fe): unflex an-note and flatten drawer background
A second `.an-note` block overrode only the type, so `display: flex` from the illustrated-note rule leaked into every note and laid prose out as flex items -- the "hard to read" Danh saw. The flex layout is now its own class, the two prose notes are two sentences each, and the drawer drops its lighter ground so the panel reads as one surface. Gate: pytest 160 passed + 7 skipped, 2 runs; every plain note is block. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>2bdb082 -
fix(fe): replace corrupted details marker with css caret
The Technical details summary carried a mangled escape in `content` (0x15 followed by "B8"), which rendered as a broken box. The marker is now a triangle drawn from borders that rotates 90 degrees when open -- no glyph, no font dependency, nothing left to mangle. Gate: pytest 160 passed + 7 skipped, 2 runs; computed content is empty and the open state measures matrix(0, 1, -1, 0, 0, 0). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>15641ee -
feat(fe): simplify shot list to spin and power columns
Eight debug columns asked a player to read V0, phi, a/b and RMSE; the list now shows what spin and how hard, and those numbers stay one click away on the detail page under Technical details. Contract test rekeyed to the five columns per the 24/08 design decision, not loosened: the old headers are now asserted ABSENT. Gate: pytest 160 passed + 7 skipped, 2 runs; demo 14 rows checked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>dded2ea -
feat(fe): collapse technical numbers behind details toggle
The model-versus-analytic table, warnings, re-simulation toggle and raw JSON link are moved, not rewritten, into one closed "Technical details" drawer, so the hero is what the page opens on. A shot with no parameters forces it open -- the reason inside is then all there is. Gate: pytest 160 passed + 7 skipped, 3 runs; drawer checked by hand on the demo (closed by default, resim toggle and RMSE line unchanged). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>af94a97 -
feat(fe): always-on spin+power hero on shot detail
A player reads two things: what spin and how hard. The widget carrying both waited for a replay, so the page opened on debug numbers instead. It now leads the page whenever the shot has a shotnet block, with a plain-English gloss and a POWER_TIERS label beside the m/s figure. Gate: pytest 160 passed + 7 skipped, 3 runs; demo 14 shots checked by hand (hero, caveat, downgrade badge, suspect box, mobile quickbar). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>feffb09 -
chore(fe): remove dead CSS rules of the removed tabs
The suggestion engine, drills, QR and TV sections went at the port but their styles stayed, so reading style.css suggested UI that does not exist. Gate: 184 -> 103 rules (81 cut), style.css 904 -> 567 lines, braces balanced; computed styles of every element across list, detail, corner overlay and quickbar at 1280x800 and 375x812 are unchanged (0 of 423 differ). Kept: .anv-badge-* and .banner.error/warn/win, both built at runtime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>6aaba6f -
refactor(fe): translate front-end UI strings to English
Second half of the one-language decision: the server already speaks English, so the page had to follow or the app would read as a mix. Gate: pytest 68 passed; 0 Vietnamese lines left outside demo_analyzer; id/class multiset and tag sequence of index.html identical to HEAD (109 attributes); list and detail pages read clean, no console errors. Also carries the CLAUDE.md language rule and the new `fe` scope. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>fae8ae3 -
refactor(analyzer): translate server-generated strings to English
The app is going one language; Vietnamese error and warning text in an otherwise English code base made every reader switch twice. Gate: pytest 68 passed; AST identical after blanking every string on all 7 modules, cv_worker only by the _CONF_LABEL_VI -> _CONF_LABEL rename (the VI suffix stops meaning anything once the labels are English). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>fe8d0e2 -
refactor(analyzer): translate identifiers and comments to English
Comments, docstrings, identifiers and test names are now English, including the Dockerfile and .dockerignore reasoning. Strings the user reads stay Vietnamese, because the product's UI language is Vietnamese: HTTP error details, per-shot reasons, warnings and the CSV headers. Semantics are unchanged -- an AST comparison with string constants blanked is identical for every module except three renamed locals; pytest 68 passed.876aff5 -
feat(analyzer): port analyzer pipeline from poolcoach-rl
Traced the import graph from the Analyzer routes: app store/transport/ embedded worker, src/poolcoach_cv end to end, cv_worker, the multi-shot contract test, the packaged demo video and the Hugging Face deploy set. The suggestion engine, drills, QR/TV and the image-scan job stay behind, so their routes, schemas, tables and front-end sections are gone with them; /api/table now reads pooltool directly (src/poolcoach_sim/table.py).da25347 -
chore(repo): scaffold poolcoach-app with layered requirements
Fresh repository for the Analyzer, split off from poolcoach-rl. Git LFS tracks *.pt/*.mp4/*.png from this first commit because Hugging Face (Xet) rejects any binary that never went through LFS, even in an old commit -- the previous repository had to push an orphan snapshot branch instead. Requirements are rebuilt from the import graph of the code being ported, not copied: core / app / cv / cv-cpu / dev.27b06b1