- 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
185 lines
10 KiB
Markdown
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.
|