- 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
10 KiB
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 and Scritchy Scratchy. Number-match inspiration: the official National Lottery Double Match procedures. Our outcomes and prices are independent; the three portrait designs originate from images supplied by the user, not scraped from lottery websites.