KAZIK/docs/SCRATCH_TICKETS.md
Игорь Федосеев ca62db53e9 feat: implement native billiards, scratch tickets, first-person dice, and city wake scene
- Billiards: add native Russian Pyramid & American Eight-Ball pool with physics prediction, controls, and regression tests
- Scratch Tickets: add dedicated counter, mat presentation, elliptical coin scraping, 4 ticket types, and payout effects
- First-Person Dice: add native dice scene, hand anchor frames, throwing animation, and table perspective
- City Wake & Weather: add arrival cutscene, alley geometry, weather/rain effects, and lighter/rocker animation
- World Dressing: add hall paintings, furniture packs, hall lighting, and scene regression tests
- Build & Tooling: update build.bat for modern MSVC toolchain, update launcher hash, and refresh shipping binaries
2026-10-16 12:37:40 +03:00

185 lines
10 KiB
Markdown

# Scratch Tickets
The runtime is MASM-only. Python only normalizes generated artwork and checks
native framebuffer captures; there is no browser or console implementation.
## Scene
A dedicated ticket counter sits at hall coordinates (29,7.5), against the
right-wing wall near Durak. Approach it from the north. It has a grounded sprite and a solid collision footprint. The
interaction opens an overhead writing mat without launching another
window. The tabletop, paper, silver foil, counter and four tilted-coin poses are original
built-in ImageGen assets. Symbols reuse the existing Clover art.
The cursor hotspot is the bottom-centre scraping rim, (32,60) in a 64x64 sprite.
Pointer coordinates use the presented viewport, including letterboxing. Left
button capture ends on mouse-up, lost capture, lost focus or scene exit. Mouse
events are processed individually, including gestures completed between ticks.
Strokes interpolate a narrow elliptical rim every three native pixels.
## Presentation
Purchasing slides the entire ticket onto the mat, including its live coating
and printed contents, with a paper-shaped contact shadow. Presentation lasts
900 ms; pointer scratching and view changes are blocked until it settles.
Portrait tickets arrive at full-ticket scale and smoothly zoom into the playing
fields over the final 350 ms. Paper/zoom use an elapsed-time smoothstep, not a
fixed animation-frame list. Exiting the scene cancels the presentation, not the
already-paid ticket.
Fresh scratches emit three silver fragments into a 64-slot cosmetic pool.
Positions/velocities use 8.8 fixed point, gravity advances in fixed 8 ms steps,
and fragments settle on the mat before expiring. A private visual seed means
particles never consume the ticket RNG. Hovering and already-erased coating
produce neither fragments nor scraping audio; scraping audio is throttled to
one short sound per 150 ms.
Positive gross payouts display a grounded chip stack beside the ticket and
five small tokens travelling toward the bank display. The cosmetic payout
clock lasts 1800 ms. Settlement itself remains immediate and single-shot;
the animation cannot issue money, delay saving or block exit/new purchases.
Portrait winning fields also receive a pale green substrate inside their exact
source-space masks. Covered numbers are never tinted through the coating.
The payout-chip sprite was generated with built-in ImageGen, then normalized
offline to 96x72 binary-alpha BGRA. Its exact prompt is stored in
`assets/sprites/scratch/prompts-v4.json`. Three original short PCM effects
(paper slide, scrape and payout tokens) are generated offline by
`tools/generators/pack_scratch_polish.py`. Rendering, animation clocks, input
gates, debris physics and audio triggers are all assembly.
Each coating has its own byte mask (up to 240x120, nine slots). Repeated strokes do not
increment coverage twice, gutters and paper cannot be scratched, and a panel
cleans its remaining fragments after 72 percent coverage. Particles use no
gameplay randomness. Rendering is deterministic and cannot reroll a ticket.
## Rules
Prices: 50, 200 or 500 fictional chips. These are price tiers, not ticket types.
The upper-right "VID BILETA" control opens a seven-row named catalogue with
thumbnails. A short click keeps its original press coordinates even if the
pointer moves before the next game tick. Escape closes the catalogue first,
then returns to the hall on a subsequent press.
The original four tickets retain their distinct artwork and rules:
- Golden Triple: nine rectangular areas; three identical symbols anywhere pay
the printed multiplier, including the stake. Exactly one winning triple or
no triples at all.
- Lucky Numbers: three winning numbers above six player numbers. A match pays
the amount printed below the matching player number. All numbers are 1..30;
winning numbers are distinct, with exactly one matching player number on a
winning ticket and none on a losing ticket. The 104x34 panels leave separate
printed labels for the winning-number row and player-number rows.
- Three in a Row: nine circular areas. Only three matching symbols in the same
horizontal row pay. A scattered or diagonal triple is not a win.
- Instant Luck: one large 240x120 area reveals the direct gross prize, including
zero for a losing ticket.
Three additional portrait tickets preserve the layouts of the user's supplied
images, localized into Russian using built-in ImageGen:
- Neon Fortune: five winning-number circles and fifteen player fields.
Matching numbers pay the amount below them; x2 pays twice the printed prize,
x10 pays ten times the printed prize.
- Ruby Jackpot: six winning-number circles and eighteen player fields.
Matching numbers and x2 work as above; the ALL marker pays every player prize.
- Treasure Wave: four winning-number circles and twelve player fields, with
matching numbers, x2 and ALL as on Ruby Jackpot.
The native bonus markers are x2, x10 and the Russian ALL abbreviation; the
adjacent native rules use the same markers. All-prizes overrides individual
awards, never paying the same prize twice. Winning numbers are distinct 1..60.
The original artwork's fixed prices and advertised maximum jackpots were
removed: the actual selected price and payout are shown by the native HUD.
Full-ticket preview preserves the 3:4 aspect ratio. Purchase automatically
zooms into the play area; the +/- control, Z and mouse wheel switch views.
Amounts are printed under player numbers in the enlarged view. Hovering a
fully uncovered field shows its actual award, never a covered field's prize.
Each supplied silver region has an exact source-space ID mask. Rounded circles
and decorated frames are not rectangular scratch surfaces. Scaling covers the
whole source footprint of each screen pixel, so both views can be fully scratched
and switching views cannot restore removed coating.
New tickets clear old coating particles, so the previous ticket cannot leave
scraps over the next one's opaque fields. Ticket types can change only before
purchase or after settlement.
Prices and type are locked while a paid ticket is active. The rules, paper
contents, outcome and amount are fixed at purchase, never while scratching.
| Outcome | Ideal 0..999 rolls | Gross Multiplier |
| --- | --- | --- |
| No prize | 600 | 0 |
| Clover | 100 | 1 |
| Lemon | 100 | 1 |
| Cherries | 110 | 2 |
| Bell | 50 | 3 |
| Diamond | 25 | 5 |
| Chest | 12 | 10 |
| Seven | 3 | 25 |
The original four tickets' ideal table has 89% gross return; the existing engine's range generator has
modulo bias, so this is not an exact measured return guarantee. The ticket is
charged once. Revealing all panels settles it once, with the existing billion-
chip balance cap (one panel for Instant Luck, nine for the other original types,
20/24/16 for the portrait types). Buttons
cannot buy a second ticket while one is active.
Portrait generation uses 0..999 outcome rolls: 600 lose, 300 have one number
match, 80 have x2. Neon uses the remaining 20 for x10. Ruby and Sea use 15 for
two matching numbers and 5 for all-prizes. One-match amounts are weighted
1/2/5/25 times price (Neon cutoffs 700/900/990; Ruby/Sea 650/850/980).
Two-match prizes each equal price; all-prizes fields each equal price/5.
This is fictional-chip gameplay, not a representation of real ticket odds.
Completed tickets show gross PAYOUT and signed NET RESULT (gross minus price)
separately. A 50-chip ticket paying 50 has net zero, not +50. Golden Triple
highlights its three matching symbols and prints price x multiplier = payout.
An unfinished ticket survives trips into the hall during the current launch.
On a normal application close its already-generated outcome is automatically
settled and the balance saved. It is not resumed as a partially scratched
ticket after relaunch. Forced termination can bypass normal shutdown handling,
as with other in-progress games; no new per-profile save format is introduced.
## Verification
`scratch_syntax.asm` checks the isolated module before the full build.
`test_scratch_module.ps1` is a fast native iteration harness: it links fresh
scratch MASM against a test-only copy of completed native helper code. Only
COFF symbol visibility changes in the copy; instructions and relocations are
not modified. It is not a substitute for the full action/shipping builds.
The native action suite checks capture/release/focus/negative input, viewport
mapping, hover versus held input, fast connected strokes, disconnected strokes,
coverage, hall-return preservation, all price tiers, 500 immutable ticket
layouts, another 200 layouts for each of the four kinds, rule-derived prizes,
type locks, circular clipping, full physical uncovering of every layout,
single settlement and the balance cap. An additional 500 generated boards per
portrait kind are checked against independently derived number/bonus payouts;
fixed bonus fixtures cover x2/x10/all-prizes. Tests also cover all 60 physical
fields, both scales, catalogue clicks after pointer movement, price selection,
paid-type locks, modal Escape and positive/zero/negative net results.
Presentation tests additionally compare 186 native captures at equal timestamps
with 10/15/30 ms updates, exercise blocked scratching during delivery, compare
25 payout frames, assert unchanged bankroll/RNG under cosmetic updates, check
single-shot sound triggers/cooldowns and expire all 64 particle slots.
The suite writes ready, covered,
partly scratched, completed, missing-art and per-kind frames.
Generated asset prompts: `assets/sprites/scratch/prompts-v1.json` and
`assets/sprites/scratch/prompts-v2.json`. Portrait provenance and recovered edit
briefs are in `assets/sprites/scratch/prompts-v3.json` (briefs are not verbatim
tool prompts). Native stroke sequences and the hall
counter can be captured with `tools/tests/test_scratch_probe.ps1`; its 114 native
frames include 26 scratch frames per portrait kind and view-switch coverage
preservation assertions. The probe
links the tested game's rendering procedures, not a Python recreation.
Mechanic references, not source-code or art dependencies:
[Scratch the Ticket](https://store.steampowered.com/app/4626940/Scratch_the_Ticket/)
and [Scritchy Scratchy](https://store.steampowered.com/app/3948120/Scritchy_Scratchy/?l=russian).
Number-match inspiration: the official National Lottery
[Double Match procedures](https://www.national-lottery.co.uk/c/files/scratchcards/doublematch.pdf~3).
Our outcomes and prices are independent; the three portrait designs originate
from images supplied by the user, not scraped from lottery websites.