diff --git a/.vscode/ios-sim-debug.sh b/.vscode/ios-sim-debug.sh new file mode 100755 index 0000000..42291df --- /dev/null +++ b/.vscode/ios-sim-debug.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# preLaunchTask for "Debug SxChess (iOS sim)": build SxChess for the iOS +# simulator with debug info, install it to a single simulator, and launch it +# PAUSED for a debugger (--wait-for-debugger). The launch.json `custom` config +# then `target create`s the local binary (so the .dSYM resolves breakpoints) +# and `process attach -n SxChess`es the waiting process. +set -e + +SX="/Users/agra/projects/sx/zig-out/bin/sx" +DSYMUTIL="/opt/homebrew/opt/llvm@19/bin/dsymutil" +# Reuse ONE simulator (sx-test-ios18). Swap the UDID for a different device. +SIM="E8DF755C-997D-49D7-9DB0-CFA48F8254CD" +APP="sx-out/ios/M3te.app" +BUNDLE="co.swipelab.m3te" + +# Debug build: -O0 + DWARF (--emit-obj keeps the object), then a portable .dSYM. +"$SX" build --target ios-sim --emit-obj main.sx +"$DSYMUTIL" "$APP/M3te" + +# Boot the sim (idempotent) and surface the Simulator UI. +xcrun simctl boot "$SIM" 2>/dev/null || true +xcrun simctl bootstatus "$SIM" >/dev/null 2>&1 || true +open -a Simulator + +xcrun simctl install "$SIM" "$APP" +xcrun simctl terminate "$SIM" "$BUNDLE" >/dev/null 2>&1 || true + +# Launch paused; the app waits at its entry until lldb attaches and continues. +xcrun simctl launch --wait-for-debugger "$SIM" "$BUNDLE" diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..050a629 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug M3te (macOS)", + "program": "${workspaceFolder}/sx-out/macos/M3te.app/Contents/MacOS/M3te", + "cwd": "${workspaceFolder}", + "preLaunchTask": "sx build (macos debug)" + }, + { + "type": "lldb", + "request": "custom", + "name": "Debug M3te (iOS sim)", + "preLaunchTask": "sx build+launch (ios-sim debug)", + "targetCreateCommands": [ + "target create '${workspaceFolder}/sx-out/ios/M3te.app/M3te'" + ], + "processCreateCommands": [ + "process attach -n M3te", + "process handle SIGSTOP --notify false --stop false --pass false", + "process continue" + ] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..327015b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,23 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "sx build (macos debug)", + "detail": "Build M3te for macOS with DWARF (-O0) and collect a .dSYM for lldb.", + "type": "shell", + "command": "/Users/agra/projects/sx/zig-out/bin/sx build --target macos --emit-obj main.sx && /opt/homebrew/opt/llvm@19/bin/dsymutil sx-out/macos/M3te.app/Contents/MacOS/M3te", + "options": { "cwd": "${workspaceFolder}" }, + "group": "build", + "problemMatcher": [] + }, + { + "label": "sx build+launch (ios-sim debug)", + "detail": "Build for the iOS simulator (-O0 + .dSYM), install, and launch paused for the debugger.", + "type": "shell", + "command": "${workspaceFolder}/.vscode/ios-sim-debug.sh", + "options": { "cwd": "${workspaceFolder}" }, + "group": "build", + "problemMatcher": [] + } + ] +} diff --git a/audio.sx b/audio.sx index 3e75b61..cca1698 100644 --- a/audio.sx +++ b/audio.sx @@ -15,12 +15,12 @@ // AudioToolbox — System Sound Services. SystemSoundID is a UInt32; OSStatus a // SInt32 (0 == noErr); the clip's file is passed as a CFURLRef (opaque ptr). -AudioServicesCreateSystemSoundID :: (url: *void, out_id: *u32) -> s32 #foreign; +AudioServicesCreateSystemSoundID :: (url: *void, out_id: *u32) -> i32 #foreign; AudioServicesPlaySystemSound :: (sound_id: u32) #foreign; // CoreFoundation — build a file CFURL from an absolute path. `len` is a CFIndex // (long); `is_dir` a Boolean (unsigned char); a NULL allocator = default. -CFURLCreateFromFileSystemRepresentation :: (allocator: *void, buffer: *u8, len: s64, is_dir: s8) -> *void #foreign; +CFURLCreateFromFileSystemRepresentation :: (allocator: *void, buffer: *u8, len: i64, is_dir: i8) -> *void #foreign; CFRelease :: (cf: *void) #foreign; // libc — getcwd to absolutize the bundle-relative asset path. The platform @@ -80,7 +80,7 @@ GameAudio :: struct { // Pick the ascending cascade clip by clamping the cascade depth into the // combo1..combo5 range (see `cascade_cue_index`). - play_cascade :: (self: *GameAudio, depth: s64) { + play_cascade :: (self: *GameAudio, depth: i64) { inline if OS != .ios { return; } if !self.loaded { return; } idx := cascade_cue_index(depth); @@ -107,7 +107,7 @@ GameAudio :: struct { // `log show` shows the clip stepping up with cascade depth. Literals only — the // string→NSString bridge needs NUL-terminated bytes (a formatted string may not // be). `idx` is a clamped `cascade_cue_index`, so it is always 0..COMBO_CLIPS-1. -cascade_cue_name :: (idx: s64) -> string { +cascade_cue_name :: (idx: i64) -> string { if idx <= 0 { return "[sx] audio: cue combo1"; } if idx == 1 { return "[sx] audio: cue combo2"; } if idx == 2 { return "[sx] audio: cue combo3"; } @@ -118,7 +118,7 @@ cascade_cue_name :: (idx: s64) -> string { // Cascade depth (number of cleared rounds) → combo clip index 0..COMBO_CLIPS-1 // (combo1..combo5). Clamps: depth <= 1 → 0, depth >= 5 → 4. Pure arithmetic and // OS-agnostic so it can be snapshot-tested headlessly (P10.4). -cascade_cue_index :: (depth: s64) -> s64 { +cascade_cue_index :: (depth: i64) -> i64 { if depth <= 1 { return 0; } if depth >= COMBO_CLIPS { return COMBO_CLIPS - 1; } depth - 1 @@ -145,7 +145,7 @@ load_system_sound :: (name: string) -> u32 { if getcwd(@cwd_buf[0], 1024) == null { return 0; } cwd : string = ---; cwd.ptr = @cwd_buf[0]; - cwd.len = cast(s64) c_strlen(@cwd_buf[0]); + cwd.len = cast(i64) c_strlen(@cwd_buf[0]); // CFURLCreateFromFileSystemRepresentation takes an explicit byte length, so // the formatted path needs no NUL terminator. @@ -169,6 +169,6 @@ g_audio : *GameAudio = null; sfx_swap :: () { if g_audio != null { g_audio.play_swap(); } } sfx_match :: () { if g_audio != null { g_audio.play_match(); } } -sfx_cascade :: (depth: s64) { if g_audio != null { g_audio.play_cascade(depth); } } +sfx_cascade :: (depth: i64) { if g_audio != null { g_audio.play_cascade(depth); } } sfx_win :: () { if g_audio != null { g_audio.play_win(); } } sfx_lose :: () { if g_audio != null { g_audio.play_lose(); } } diff --git a/board.sx b/board.sx index 36b2ea1..af6f4f7 100644 --- a/board.sx +++ b/board.sx @@ -39,36 +39,36 @@ EMPTY_CHAR :: 46; // '.' gem_char :: (g: Gem) -> u8 { if g == .empty { return EMPTY_CHAR; } - GEM_CHARS[cast(s64) g] + GEM_CHARS[cast(i64) g] } // ── Deterministic RNG ───────────────────────────────────────────────────── // A 32-bit linear congruential generator (Numerical Recipes constants), -// carried in an s64 and masked back to 32 bits after every step so the +// carried in an i64 and masked back to 32 bits after every step so the // stream is identical regardless of host integer width. The state*MUL+ADD -// product stays well under s64 range, so no intermediate overflow. Any seed +// product stays well under i64 range, so no intermediate overflow. Any seed // (including 0) yields a valid stream — an LCG has no forbidden state. RNG_MASK32 :: 0xFFFFFFFF; RNG_MUL :: 1664525; RNG_ADD :: 1013904223; Rng :: struct { - state: s64; + state: i64; // Advance and return the next 32-bit value. - next_u32 :: (self: *Rng) -> s64 { + next_u32 :: (self: *Rng) -> i64 { self.state = (self.state * RNG_MUL + RNG_ADD) & RNG_MASK32; self.state } // Uniform-ish value in [0, n). Uses the high bits, whose period is far // longer than the low bits of an LCG. - next_range :: (self: *Rng, n: s64) -> s64 { + next_range :: (self: *Rng, n: i64) -> i64 { (self.next_u32() >> 16) % n } } -rng_seeded :: (seed: s64) -> Rng { +rng_seeded :: (seed: i64) -> Rng { Rng.{ state = seed & RNG_MASK32 } } @@ -106,7 +106,7 @@ Board :: struct { // round's base points (see `score_round`), and `resolve` adds each cascade // round's base scaled by `combo_multiplier` (P3.2). The HUD (P4.4) reads this // field. A hand-built board must zero this before accumulating. - score: s64; + score: i64; // Turn accounting (P3.3). `moves_made` counts the swaps actually COMMITTED — // only a legal swap (one that resolved into >=1 match) via `commit_swap` @@ -115,15 +115,15 @@ Board :: struct { // is derived from the two, so there is a single source of truth and the // counters can never drift apart. A hand-built board must set both before // committing swaps. - moves_made: s64; - move_limit: s64; + moves_made: i64; + move_limit: i64; // Per-level score goal (P7.1). `init` sets it to DEFAULT_TARGET_SCORE; // `level_status` reads it to decide a win (`score >= target_score`). A // hand-built board must set this before its status is read. - target_score: s64; + target_score: i64; - idx :: (col: s64, row: s64) -> s64 { + idx :: (col: i64, row: i64) -> i64 { row * BOARD_COLS + col } @@ -131,15 +131,15 @@ Board :: struct { // to 0 when the budget is spent (and below it only if a caller keeps // committing past the budget — see DEFAULT_MOVE_LIMIT). The turn/goal loop // (P7) reads this to decide when the game ends. - moves_remaining :: (self: *Board) -> s64 { + moves_remaining :: (self: *Board) -> i64 { self.move_limit - self.moves_made } - at :: (self: *Board, col: s64, row: s64) -> Gem { + at :: (self: *Board, col: i64, row: i64) -> Gem { self.cells[Board.idx(col, row)] } - set :: (self: *Board, col: s64, row: s64, g: Gem) { + set :: (self: *Board, col: i64, row: i64, g: Gem) { self.cells[Board.idx(col, row)] = g; } @@ -149,7 +149,7 @@ Board :: struct { // already-placed cells to its left or above is excluded, and the gem is // drawn from the remaining allowed types. At most two types are ever // excluded, so a choice always remains. - init :: (self: *Board, seed: s64) { + init :: (self: *Board, seed: i64) { self.rng = rng_seeded(seed); self.score = 0; self.moves_made = 0; @@ -165,7 +165,7 @@ Board :: struct { // Choose a gem for (col, row) that can't extend an existing run leftward or // upward. Pure given the board's already-placed prefix and the RNG state. -pick_gem :: (board: *Board, rng: *Rng, col: s64, row: s64) -> Gem { +pick_gem :: (board: *Board, rng: *Rng, col: i64, row: i64) -> Gem { forbidden : [GEM_COUNT]bool = ---; for 0..GEM_COUNT (t) { forbidden[t] = false; } @@ -173,14 +173,14 @@ pick_gem :: (board: *Board, rng: *Rng, col: s64, row: s64) -> Gem { if col >= 2 { left := board.at(col - 1, row); if left == board.at(col - 2, row) { - forbidden[cast(s64) left] = true; + forbidden[cast(i64) left] = true; } } // Two same gems immediately above → a third of that type matches. if row >= 2 { up := board.at(col, row - 1); if up == board.at(col, row - 2) { - forbidden[cast(s64) up] = true; + forbidden[cast(i64) up] = true; } } @@ -223,12 +223,12 @@ board_dump :: (self: *Board) -> string { MatchMask :: struct { cells: [BOARD_CELLS]bool; - at :: (self: *MatchMask, col: s64, row: s64) -> bool { + at :: (self: *MatchMask, col: i64, row: i64) -> bool { self.cells[Board.idx(col, row)] } - count :: (self: *MatchMask) -> s64 { - n : s64 = 0; + count :: (self: *MatchMask) -> i64 { + n : i64 = 0; for 0..BOARD_CELLS (i) { if self.cells[i] { n += 1; } } n } @@ -237,7 +237,7 @@ MatchMask :: struct { // Mark a closed span of cells along one axis. `vertical` picks the axis; `fixed` // is the constant coordinate (the row for a horizontal span, the column for a // vertical one) and the span covers `start..end` of the moving coordinate. -mark_run :: (m: *MatchMask, vertical: bool, fixed: s64, start: s64, end: s64) { +mark_run :: (m: *MatchMask, vertical: bool, fixed: i64, start: i64, end: i64) { for start..end (i) { if vertical { m.cells[Board.idx(fixed, i)] = true; @@ -317,8 +317,8 @@ dump_matches :: (b: *Board, m: *MatchMask) -> string { // A board cell address. Kept separate from the row-major index so swap callers // and the move enumeration speak in (col, row) like the rest of the model. Cell :: struct { - col: s64; - row: s64; + col: i64; + row: i64; } // Exchange the gems of two cells, in place. `swap` is its own inverse: calling @@ -413,8 +413,8 @@ dump_swaps :: (swaps: *List(Swap)) -> string { // unchanged. Returns the number of cells cleared. `mask` is the matched-cell SET // from find_matches, so overlapping L/T shapes (already unioned into a single // `true` per shared cell) clear as one set with no double-counting. -clear_cells :: (board: *Board, mask: *MatchMask) -> s64 { - cleared : s64 = 0; +clear_cells :: (board: *Board, mask: *MatchMask) -> i64 { + cleared : i64 = 0; for 0..BOARD_CELLS (i) { if mask.cells[i] { board.cells[i] = .empty; @@ -428,7 +428,7 @@ clear_cells :: (board: *Board, mask: *MatchMask) -> s64 { // cells cleared — 0 when there are no matches, in which case the board is left // unchanged. The count drives later cascade/scoring (P2.2+): a non-zero result // means the board changed and the resolution loop should continue. -clear_matches :: (board: *Board) -> s64 { +clear_matches :: (board: *Board) -> i64 { m := find_matches(board); clear_cells(board, @m) } @@ -489,9 +489,9 @@ collapse :: (board: *Board) -> bool { // only ever yields ordinals 0..GEM_COUNT, so a hole is never refilled with // `.empty`; afterwards the board has no holes left. Returns the number of cells // filled (0 on a board that had none). -refill :: (board: *Board) -> s64 { +refill :: (board: *Board) -> i64 { rng := @board.rng; - filled : s64 = 0; + filled : i64 = 0; for 0..BOARD_ROWS (row) { for 0..BOARD_COLS (col) { if board.at(col, row) == .empty { @@ -526,11 +526,11 @@ refill :: (board: *Board) -> s64 { // make "did this settle clear a 4 / 5+ run" observable. `had_len4` / // `had_len5_plus` are the boolean view of the same counts. Cascade :: struct { - depth: s64; - cleared: List(s64); - awarded: s64; - len4: s64; - len5_plus: s64; + depth: i64; + cleared: List(i64); + awarded: i64; + len4: i64; + len5_plus: i64; had_len4 :: (self: *Cascade) -> bool { self.len4 > 0 @@ -546,7 +546,7 @@ Cascade :: struct { // number of cells cleared this round — 0 iff the board was already stable, in // which case nothing moves and no gem is drawn. `resolve` repeats this until it // returns 0. -resolve_step :: (board: *Board) -> s64 { +resolve_step :: (board: *Board) -> i64 { cleared := clear_matches(board); if cleared == 0 { return 0; } collapse(board); @@ -560,7 +560,7 @@ resolve_step :: (board: *Board) -> s64 { // Each round adds `score_round * combo_multiplier(round)` (round 1-based) to // `Board.score`; an already-stable board returns depth 0, awards 0, untouched. resolve :: (board: *Board) -> Cascade { - result := Cascade.{ depth = 0, cleared = List(s64).{}, awarded = 0, len4 = 0, len5_plus = 0 }; + result := Cascade.{ depth = 0, cleared = List(i64).{}, awarded = 0, len4 = 0, len5_plus = 0 }; while true { // Read the round's base points AND its special-match tally while the runs // are still on the board: `resolve_step` clears them, so both have to be @@ -604,15 +604,15 @@ SCORE_RUN_5_PLUS :: 100; // vertical one) and the run covers `start..start+len` of the moving coordinate. Run :: struct { vertical: bool; - fixed: s64; - start: s64; - len: s64; + fixed: i64; + start: i64; + len: i64; } // Base points for a single maximal run, by length. Runs are always length >= 3 // (shorter spans are not enumerated), so 3 is the floor; 5 and longer all score // the top tier. -run_score :: (len: s64) -> s64 { +run_score :: (len: i64) -> i64 { if len <= 3 { return SCORE_RUN_3; } if len == 4 { return SCORE_RUN_4; } SCORE_RUN_5_PLUS @@ -666,9 +666,9 @@ find_runs :: (b: *Board) -> List(Run) { // read-only — it inspects the board but changes nothing, so it must be called // BEFORE the round's clear, while the runs are still on the board. A board with // no run scores 0. -score_round :: (board: *Board) -> s64 { +score_round :: (board: *Board) -> i64 { runs := find_runs(board); - total : s64 = 0; + total : i64 = 0; for 0..runs.len (i) { total += run_score(runs.items[i].len); } @@ -679,7 +679,7 @@ score_round :: (board: *Board) -> s64 { // `score` total and return them. The single-round accumulation primitive; the // cascade loop (`resolve`) instead scales each round by `combo_multiplier` // (P3.2). Neither path changes `score_round`. -add_round_score :: (board: *Board) -> s64 { +add_round_score :: (board: *Board) -> i64 { points := score_round(board); board.score += points; points @@ -694,7 +694,7 @@ add_round_score :: (board: *Board) -> s64 { // multi-round chain strictly beats the same clears scored flat. `resolve` // accumulates `score_round * combo_multiplier(round)` per round into `Board.score` // and reports the sum as `Cascade.awarded`. -combo_multiplier :: (round: s64) -> s64 { +combo_multiplier :: (round: i64) -> i64 { round } @@ -711,8 +711,8 @@ combo_multiplier :: (round: s64) -> s64 { // "did any occur" lives on `Cascade` (`had_len4` / `had_len5_plus`) for the // whole settle; a single round reads these counts directly. SpecialCounts :: struct { - len4: s64; - len5_plus: s64; + len4: i64; + len5_plus: i64; } // Count the board's currently-matched runs that hit a special length, by the @@ -766,7 +766,7 @@ dump_runs :: (runs: *List(Run)) -> string { PlayerMove :: struct { legal: bool; cascade: Cascade; - moves_remaining: s64; + moves_remaining: i64; } // Attempt the player's intended swap of two adjacent cells. If the swap is legal @@ -779,7 +779,7 @@ PlayerMove :: struct { // spent (that is the P7 turn-loop's call) — see DEFAULT_MOVE_LIMIT. commit_swap :: (board: *Board, a: Cell, b: Cell) -> PlayerMove { if !swap_legal(board, a, b) { - empty := Cascade.{ depth = 0, cleared = List(s64).{}, awarded = 0, len4 = 0, len5_plus = 0 }; + empty := Cascade.{ depth = 0, cleared = List(i64).{}, awarded = 0, len4 = 0, len5_plus = 0 }; return PlayerMove.{ legal = false, cascade = empty, moves_remaining = board.moves_remaining() }; } swap(board, a, b); @@ -900,7 +900,7 @@ reshuffle_if_deadlocked :: (board: *Board) -> bool { // seed → identical starting layout), zeroes `score` and `moves_made`, and // restores the default move budget and score goal, so `level_status` reads // `in_progress` again. The entry point P7.2's restart button calls. -restart :: (board: *Board, seed: s64) { +restart :: (board: *Board, seed: i64) { board.init(seed); } @@ -930,7 +930,7 @@ TurnResult :: struct { play_turn :: (board: *Board, a: Cell, b: Cell) -> TurnResult { status := level_status(board); if status != .in_progress { - empty := Cascade.{ depth = 0, cleared = List(s64).{}, awarded = 0, len4 = 0, len5_plus = 0 }; + empty := Cascade.{ depth = 0, cleared = List(i64).{}, awarded = 0, len4 = 0, len5_plus = 0 }; frozen := PlayerMove.{ legal = false, cascade = empty, moves_remaining = board.moves_remaining() }; return TurnResult.{ accepted = false, move = frozen, status = status, reshuffled = false }; } diff --git a/board_anim.sx b/board_anim.sx index 9dc536f..a22ca61 100644 --- a/board_anim.sx +++ b/board_anim.sx @@ -109,7 +109,7 @@ bad_swap_bounce :: (t: f32) -> f32 { // ease_in_cubic so each column still accelerates under gravity within its window. // `tests/easing.sx` pins f(0)=0, f(1)=1, monotonicity, and the cascade ordering. FALL_STAGGER_MAX :f32: 0.30; -fall_stagger_t :: (t: f32, col: s64) -> f32 { +fall_stagger_t :: (t: f32, col: i64) -> f32 { delay := FALL_STAGGER_MAX * (cast(f32) col / cast(f32) (BOARD_COLS - 1)); window := 1.0 - FALL_STAGGER_MAX; lt := (t - delay) / window; @@ -123,7 +123,7 @@ fall_stagger_t :: (t: f32, col: s64) -> f32 { // at `1 - FALL_STAGGER_MAX`; the last column lands exactly at 1.0. The landing // squash-bounce (P17.3) ages from this instant per column, so the squash begins // the moment a gem touches its cell rather than at a flat whole-row settle. -fall_landing_frac :: (col: s64) -> f32 { +fall_landing_frac :: (col: i64) -> f32 { (1.0 - FALL_STAGGER_MAX) + FALL_STAGGER_MAX * (cast(f32) col / cast(f32) (BOARD_COLS - 1)) } @@ -132,7 +132,7 @@ fall_landing_frac :: (col: s64) -> f32 { // per-round bounce ages from. Round k's fall starts after the swap, k clear+fall // pairs, and that round's own clear; column `col` then lands `fall_landing_frac` // of the fall window into it. Pure + headless, mirrors `phase`'s segment walk. -round_land_time :: (k: s64, col: s64) -> f32 { +round_land_time :: (k: i64, col: i64) -> f32 { SWAP_ANIM_DUR + cast(f32) k * (CLEAR_ANIM_DUR + FALL_ANIM_DUR) + CLEAR_ANIM_DUR + fall_landing_frac(col) * FALL_ANIM_DUR } @@ -159,10 +159,10 @@ clear_ripple_t :: (t: f32, u: f32) -> f32 { // The diagonal (col+row) extent of a round's matched cells — the span the ripple // ranks each matched gem across. `hi < lo` only if the mask is empty. -ClearDiag :: struct { lo: s64; hi: s64; } +ClearDiag :: struct { lo: i64; hi: i64; } clear_diag_span :: (m: *MatchMask) -> ClearDiag { - lo : s64 = (BOARD_COLS - 1) + (BOARD_ROWS - 1) + 1; - hi : s64 = -1; + lo : i64 = (BOARD_COLS - 1) + (BOARD_ROWS - 1) + 1; + hi : i64 = -1; for 0..BOARD_CELLS (i) { if m.cells[i] { d := (i % BOARD_COLS) + (i / BOARD_COLS); @@ -178,7 +178,7 @@ clear_diag_span :: (m: *MatchMask) -> ClearDiag { // PER ROUND (not across the board) lets even a small 3-match ripple across the // full stagger budget. A degenerate span (every matched cell on one diagonal) // ranks all gems 0, so they pop together rather than dividing by zero. -clear_rank :: (span: ClearDiag, col: s64, row: s64) -> f32 { +clear_rank :: (span: ClearDiag, col: i64, row: i64) -> f32 { if span.hi <= span.lo { return 0.0; } cast(f32) ((col + row) - span.lo) / cast(f32) (span.hi - span.lo) } @@ -193,7 +193,7 @@ clear_rank :: (span: ClearDiag, col: s64, row: s64) -> f32 { AnimRound :: struct { before: [BOARD_CELLS]Gem; matched: MatchMask; - src: [BOARD_CELLS]s64; + src: [BOARD_CELLS]i64; after: [BOARD_CELLS]Gem; } @@ -210,7 +210,7 @@ AnimMove :: struct { pre: [BOARD_CELLS]Gem; rounds: List(AnimRound); final: [BOARD_CELLS]Gem; - awarded: s64; + awarded: i64; } // The most recent round at or before `kmax` that dropped a MOVED gem onto @@ -221,7 +221,7 @@ AnimMove :: struct { // ages from its LATEST arrival, never a stale earlier one. Pure + headless: the // per-round bounce (render_fall/clear) and the final-settle stamp share this so // one envelope plays continuously across every seam. -delivering_round :: (mv: *AnimMove, i: s64, kmax: s64) -> s64 { +delivering_round :: (mv: *AnimMove, i: i64, kmax: i64) -> i64 { row := i / BOARD_COLS; k := kmax; while k >= 0 { @@ -312,7 +312,7 @@ AnimPhaseKind :: enum { swap; clear; fall; done; } AnimPhase :: struct { kind: AnimPhaseKind; - round: s64; + round: i64; t: f32; } @@ -327,7 +327,7 @@ BoardAnim :: struct { // so the frame loop's per-round SFX is edge-triggered: a round's cue fires once, // when its clear begins, never re-fired every frame. Reset whenever a move // (re)starts; advanced by the frame loop as rounds clear. - cascade_fired: s64; + cascade_fired: i64; init :: (self: *BoardAnim) { self.active = false; @@ -383,11 +383,11 @@ BoardAnim :: struct { // have fired by now (clamped to the move's round count). The frame loop diffs it // against `BoardAnim.cascade_fired` to play one cue per newly-cleared round. Pure + // headless so the per-round playback is snapshot-testable without audio. -cascade_rounds_started :: (elapsed: f32, num_rounds: s64) -> s64 { +cascade_rounds_started :: (elapsed: f32, num_rounds: i64) -> i64 { if num_rounds <= 0 { return 0; } if elapsed < SWAP_ANIM_DUR { return 0; } seg := CLEAR_ANIM_DUR + FALL_ANIM_DUR; - started := cast(s64) ((elapsed - SWAP_ANIM_DUR) / seg) + 1; + started := cast(i64) ((elapsed - SWAP_ANIM_DUR) / seg) + 1; if started > num_rounds { return num_rounds; } started } diff --git a/board_fx.sx b/board_fx.sx index f055ab5..6d315f3 100644 --- a/board_fx.sx +++ b/board_fx.sx @@ -48,7 +48,7 @@ FX_COMBO_LABEL_GAP :f32: 0.12; // gap (cell units) between the label and +poin // order (red, orange, yellow, green, blue, purple). Saturated a touch past the // pastel — the low channel is trimmed while the dominant/mid channel is lifted — // so every burst pops as a punchier colour without losing luminance. -fx_tint :: (i: s64) -> Color { +fx_tint :: (i: i64) -> Color { if i == 0 { return Color.{ r = 255, g = 92, b = 62, a = 255 }; } if i == 1 { return Color.{ r = 255, g = 164, b = 44, a = 255 }; } if i == 2 { return Color.{ r = 255, g = 240, b = 72, a = 255 }; } @@ -67,7 +67,7 @@ FX_POPUP_COMBO_HOT :: Color.{ r = 255, g = 248, b = 214, a = 255 }; // hot n // lockstep with the cascade SFX cue. Pure arithmetic, OS-agnostic, and the // equivalence to `cascade_cue_index` is locked headlessly (tests/fx_combo.sx). FX_COMBO_MAX_LEVEL :: 4; // == audio.sx COMBO_CLIPS - 1 -fx_combo_level :: (depth: s64) -> s64 { +fx_combo_level :: (depth: i64) -> i64 { if depth <= 1 { return 0; } if depth >= FX_COMBO_MAX_LEVEL + 1 { return FX_COMBO_MAX_LEVEL; } depth - 1 @@ -76,14 +76,14 @@ fx_combo_level :: (depth: s64) -> s64 { // Popup font size for a cascade `depth` rounds deep: a single clear (depth <= 1) // uses the plain size; a combo starts at the base combo size and grows one step // per combo level past the first, clamped at the deepest level. -fx_popup_font :: (depth: s64) -> f32 { +fx_popup_font :: (depth: i64) -> f32 { if depth <= 1 { return FX_POPUP_FONT; } FX_POPUP_COMBO_FONT + FX_POPUP_COMBO_STEP * cast(f32) (fx_combo_level(depth) - 1) } // Popup colour for a cascade `depth` rounds deep: white for a single clear, else // the gold lerped toward a hot near-white as the cascade deepens. -fx_popup_color :: (depth: s64) -> Color { +fx_popup_color :: (depth: i64) -> Color { if depth <= 1 { return FX_POPUP_COLOR; } t := cast(f32) (fx_combo_level(depth) - 1) / cast(f32) (FX_COMBO_MAX_LEVEL - 1); Color.{ @@ -101,7 +101,7 @@ fx_lerp_u8 :: (lo: u8, hi: u8, t: f32) -> u8 { // Upload an RGBA buffer as a texture, returning its handle. Mirrors // board_view.load_texture's upload half but takes an in-memory buffer (the // per-colour tinted particle) instead of a file path. -upload_rgba :: (pixels: [*]u8, w: s32, h: s32, gpu: ?GPU) -> u32 { +upload_rgba :: (pixels: [*]u8, w: i32, h: i32, gpu: ?GPU) -> u32 { if gpu != null { return xx gpu.create_texture(w, h, .rgba8, xx pixels); } @@ -128,22 +128,22 @@ BoardFxAssets :: struct { } load :: (self: *BoardFxAssets, gpu: ?GPU) { - w : s32 = 0; - h : s32 = 0; - ch : s32 = 0; + w : i32 = 0; + h : i32 = 0; + ch : i32 = 0; src : [*]u8 = xx stbi_load("assets/fx/particle.png", @w, @h, @ch, 4); if xx src == 0 { out("WARNING: could not load assets/fx/particle.png\n"); self.loaded = false; return; } - n := cast(s64) w * cast(s64) h; + n := cast(i64) w * cast(i64) h; buf : [*]u8 = xx context.allocator.alloc_bytes(n * 4); // Loop locals are hoisted: a block-scoped local declared inside a body // that runs hundreds of thousands of times grows the stack per iteration // (sx codegen), so the per-pixel tint loop only ASSIGNS pre-declared vars. - i : s64 = 0; - o : s64 = 0; + i : i64 = 0; + o : i64 = 0; for 0..GEM_COUNT (t) { col := fx_tint(t); i = 0; @@ -168,7 +168,7 @@ BoardFxAssets :: struct { FxParticle :: struct { col: f32; row: f32; - tint: s64; + tint: i64; delay: f32; age: f32; life: f32; @@ -184,8 +184,8 @@ FxParticle :: struct { FxPopup :: struct { col: f32; row: f32; - points: s64; - depth: s64; + points: i64; + depth: i64; delay: f32; age: f32; life: f32; @@ -237,7 +237,7 @@ BoardFx :: struct { self.particles.append(FxParticle.{ col = cast(f32) col + 0.5, row = cast(f32) row + 0.5, - tint = cast(s64) g, + tint = cast(i64) g, delay = t0 + rdelay, age = 0.0, life = FX_BURST_LIFE, @@ -250,9 +250,9 @@ BoardFx :: struct { // One popup for the whole move at the first clear's centroid. rd0 := @mv.rounds.items[0]; - sc : s64 = 0; - sr : s64 = 0; - cnt : s64 = 0; + sc : i64 = 0; + sr : i64 = 0; + cnt : i64 = 0; for 0..BOARD_CELLS (idx) { if rd0.matched.cells[idx] { sc += idx % BOARD_COLS; @@ -275,8 +275,8 @@ BoardFx :: struct { // Advance every live FX by `dt` and drop those past their lifetime. Kept // simple: compact each list in place by overwriting dead entries. tick :: (self: *BoardFx, dt: f32) { - w : s64 = 0; - i : s64 = 0; + w : i64 = 0; + i : i64 = 0; while i < self.particles.len { p := self.particles.items[i]; p.age += dt; diff --git a/board_layout.sx b/board_layout.sx index c250856..0b8672a 100644 --- a/board_layout.sx +++ b/board_layout.sx @@ -25,7 +25,7 @@ BoardLayout :: struct { }; } - cell_frame :: (self: *BoardLayout, col: s64, row: s64) -> Frame { + cell_frame :: (self: *BoardLayout, col: i64, row: i64) -> Frame { Frame.make( self.origin.x + xx col * self.cell_size, self.origin.y + xx row * self.cell_size, @@ -44,8 +44,8 @@ BoardLayout :: struct { fx := (p.x - self.origin.x) / self.cell_size; fy := (p.y - self.origin.y) / self.cell_size; if fx < 0.0 or fy < 0.0 { return null; } - col : s64 = xx fx; - row : s64 = xx fy; + col : i64 = xx fx; + row : i64 = xx fy; if col >= BOARD_COLS or row >= BOARD_ROWS { return null; } Cell.{ col = col, row = row } } diff --git a/board_view.sx b/board_view.sx index a182fe9..f3aaa62 100644 --- a/board_view.sx +++ b/board_view.sx @@ -126,7 +126,7 @@ BoardAssets :: struct { self.loaded = self.bg_tex != 0 and self.cell_tex != 0 and self.gems_tex != 0; } - gem_uv :: (self: *BoardAssets, index: s64) -> GemUV { + gem_uv :: (self: *BoardAssets, index: i64) -> GemUV { u0 : f32 = xx index * self.cell_u; GemUV.{ uv_min = Point.{ x = u0, y = 0.0 }, @@ -139,9 +139,9 @@ BoardAssets :: struct { // failure). When a GPU backend is bound (iOS Metal) it owns the upload; the // desktop GL path falls back to a plain GL_TEXTURE_2D. load_texture :: (path: [:0]u8, gpu: ?GPU) -> u32 { - w : s32 = 0; - h : s32 = 0; - ch : s32 = 0; + w : i32 = 0; + h : i32 = 0; + ch : i32 = 0; pixels := stbi_load(path, @w, @h, @ch, 4); if pixels == null { out("WARNING: could not load texture: "); @@ -237,7 +237,7 @@ BoardView :: struct { safe: EdgeInsets; // Seed for `restart`: the same fixed seed main seeded the board with, so the // restart button reproduces the identical starting level. - seed: s64; + seed: i64; // FPS dev overlay (P20.1). `fps_on` gates the corner readout (off by default, // set only by the M3TE_FPS env pin); `fps` is the smoothed reciprocal frame // rate computed in the frame loop. Purely a render overlay. @@ -266,7 +266,7 @@ BoardView :: struct { } // Draw gem `gem_index`'s sprite-sheet column into `gf`. - draw_gem :: (self: *BoardView, ctx: *RenderContext, gf: Frame, gem_index: s64) { + draw_gem :: (self: *BoardView, ctx: *RenderContext, gf: Frame, gem_index: i64) { uv := self.assets.gem_uv(gem_index); ctx.add_image_uv(gf, self.assets.gems_tex, uv.uv_min, uv.uv_max); } @@ -285,7 +285,7 @@ BoardView :: struct { // Frame for a gem shrunk by `scale` about its cell centre — the clear // scale-out. At scale 0 the gem is a zero-size frame (gone). - gem_frame_scaled :: (self: *BoardView, col: s64, row: s64, dim: f32, scale: f32) -> Frame { + gem_frame_scaled :: (self: *BoardView, col: i64, row: i64, dim: f32, scale: f32) -> Frame { cs := self.layout.cell_size; cx := self.layout.origin.x + cast(f32) col * cs + cs * 0.5; cy := self.layout.origin.y + cast(f32) row * cs + cs * 0.5; @@ -297,7 +297,7 @@ BoardView :: struct { // sprite is scaled about its cell centre and nudged by the pose offset (both // in cell units). A resting pose reproduces gem_frame exactly, so the t==0 // idle pose draws identically to the static sprite. - gem_pose_frame :: (self: *BoardView, col: s64, row: s64, dim: f32, pose: GemPose) -> Frame { + gem_pose_frame :: (self: *BoardView, col: i64, row: i64, dim: f32, pose: GemPose) -> Frame { cs := self.layout.cell_size; cx := self.layout.origin.x + (cast(f32) col + 0.5) * cs + pose.dx * cs; cy := self.layout.origin.y + (cast(f32) row + 0.5) * cs + pose.dy * cs; @@ -311,7 +311,7 @@ BoardView :: struct { // — the wide-and-short landing impact. sq==0 reproduces gem_frame's centred // placement EXACTLY, so a gem still mid-fall (or one that never moved) draws // byte-identically to the plain fall; only a landed gem flattens. - gem_squash_frame :: (self: *BoardView, col: s64, frow: f32, dim: f32, sq: f32) -> Frame { + gem_squash_frame :: (self: *BoardView, col: i64, frow: f32, dim: f32, sq: f32) -> Frame { cs := self.layout.cell_size; cx := self.layout.origin.x + (cast(f32) col + 0.5) * cs; cy := self.layout.origin.y + (frow + 0.5) * cs; @@ -323,7 +323,7 @@ BoardView :: struct { // The per-gem animation pose for a settled cell: the always-on idle breath, // plus a squash-bounce if the cell landed recently, plus a pop if it is the // selected cell. Purely visual — composed from gem_anim's pure functions. - gem_pose_at :: (self: *BoardView, col: s64, row: s64) -> GemPose { + gem_pose_at :: (self: *BoardView, col: i64, row: i64) -> GemPose { pose := idle_pose(self.motion.clock, col, row); sq := land_squash(self.motion.land_local(Board.idx(col, row))); @@ -347,7 +347,7 @@ BoardView :: struct { // (land_squash → 0, so it draws unsquashed) and one that never moved reads 0. // render_fall passes the current round; render_clear the previous (its board is // that round's `after`), so the one bounce plays on across the fall→clear seam. - rest_squash :: (self: *BoardView, i: s64, kmax: s64, elapsed: f32) -> f32 { + rest_squash :: (self: *BoardView, i: i64, kmax: i64, elapsed: f32) -> f32 { m := delivering_round(@self.anim.move, i, kmax); if m < 0 { return 0.0; } col := i % BOARD_COLS; @@ -363,7 +363,7 @@ BoardView :: struct { if g != .empty { pose := self.gem_pose_at(col, row); gf := self.gem_pose_frame(col, row, dim, pose); - self.draw_gem(ctx, gf, cast(s64) g); + self.draw_gem(ctx, gf, cast(i64) g); } } } @@ -436,7 +436,7 @@ BoardView :: struct { if g != .empty { sq := self.rest_squash(i, last, e); gf := self.gem_squash_frame(i % BOARD_COLS, cast(f32) (i / BOARD_COLS), dim, sq); - self.draw_gem(ctx, gf, cast(s64) g); + self.draw_gem(ctx, gf, cast(i64) g); } } } @@ -457,7 +457,7 @@ BoardView :: struct { g := mv.pre[i]; if g != .empty { gf := self.gem_frame(cast(f32) (i % BOARD_COLS), cast(f32) (i / BOARD_COLS), inset, dim); - self.draw_gem(ctx, gf, cast(s64) g); + self.draw_gem(ctx, gf, cast(i64) g); } } @@ -482,18 +482,18 @@ BoardView :: struct { ga := mv.pre[ai]; if ga != .empty { gf := self.gem_frame(afc + (bfc - afc) * p, afr + (bfr - afr) * p, inset, dim); - self.draw_gem(ctx, gf, cast(s64) ga); + self.draw_gem(ctx, gf, cast(i64) ga); } gb := mv.pre[bi]; if gb != .empty { gf := self.gem_frame(bfc + (afc - bfc) * p, bfr + (afr - bfr) * p, inset, dim); - self.draw_gem(ctx, gf, cast(s64) gb); + self.draw_gem(ctx, gf, cast(i64) gb); } } // Clear segment: matched gems pop outward then collapse to nothing (a // satisfying pop, composing with the particle burst); the rest hold position. - render_clear :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: s64, e: f32, dim: f32, t: f32) { + render_clear :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: i64, e: f32, dim: f32, t: f32) { span := clear_diag_span(@rd.matched); for 0..BOARD_CELLS (i) { g := rd.before[i]; @@ -507,7 +507,7 @@ BoardView :: struct { // reaches scale 0 by t==1, keeping the seam to the fall clean. pop := clear_pop_scale(clear_ripple_t(t, clear_rank(span, col, row))); gf := self.gem_frame_scaled(col, row, dim, pop); - self.draw_gem(ctx, gf, cast(s64) g); + self.draw_gem(ctx, gf, cast(i64) g); } else { // before[k] is round k-1's settled board, so a survivor here still // carries the bounce from the round that dropped it in — continue it @@ -515,7 +515,7 @@ BoardView :: struct { // (nothing has fallen yet), keeping that frame byte-identical. sq := self.rest_squash(i, k - 1, e); gf := self.gem_squash_frame(col, cast(f32) row, dim, sq); - self.draw_gem(ctx, gf, cast(s64) g); + self.draw_gem(ctx, gf, cast(i64) g); } } } @@ -593,7 +593,7 @@ BoardView :: struct { // lockstep row; ease_in_cubic pins each column's f(1)=1, and fall_stagger_t // guarantees every column reaches 1 by t==1, so each gem lands exactly on its // cell and the seam to the next round / settled board stays invisible. - render_fall :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: s64, e: f32, dim: f32, t: f32) { + render_fall :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: i64, e: f32, dim: f32, t: f32) { for 0..BOARD_CELLS (i) { g := rd.after[i]; if g == .empty { continue; } @@ -608,7 +608,7 @@ BoardView :: struct { // has reached its cell flattens wide-and-short, then wobbles out. sq := self.rest_squash(i, k, e); gf := self.gem_squash_frame(col, cur_row, dim, sq); - self.draw_gem(ctx, gf, cast(s64) g); + self.draw_gem(ctx, gf, cast(i64) g); } } @@ -668,7 +668,7 @@ BoardView :: struct { // pin) is set, so the unset render path is byte-identical. A bright halo under // the dark text keeps the digits legible over the light background art. render_fps_overlay :: (self: *BoardView, ctx: *RenderContext, frame: Frame) { - n := cast(s64) (self.fps + 0.5); + n := cast(i64) (self.fps + 0.5); txt := format("FPS {}", n); sz := measure_text(txt, FPS_FONT); x := frame.origin.x + self.safe.left + FPS_PAD; diff --git a/docs/std-gaps.md b/docs/std-gaps.md index 271858e..8f8e9e1 100644 --- a/docs/std-gaps.md +++ b/docs/std-gaps.md @@ -25,9 +25,9 @@ the step asks for an honest accounting. ## Category: Numerics / parsing -### 1. String → number parsing (`parse_s64`, `parse_f32`) -- **m3te location:** `main.sx:156` (`parse_s64`), `main.sx:122` (`parse_f32`). -- **What it does:** decimal ASCII `string` → `s64` / `f32` (sign + integer + +### 1. String → number parsing (`parse_i64`, `parse_f32`) +- **m3te location:** `main.sx:156` (`parse_i64`), `main.sx:122` (`parse_f32`). +- **What it does:** decimal ASCII `string` → `i64` / `f32` (sign + integer + optional fractional part). - **Why general-purpose:** the exact inverse of the formatters everyone already uses; needed by any program that reads numbers from argv, env, config, or a @@ -46,7 +46,7 @@ the step asks for an honest accounting. - **m3te location:** `board.sx:55` (`Rng` struct: `next_u32` `board.sx:59`, `next_range` `board.sx:66`), `board.sx:71` (`rng_seeded`), constants `board.sx:51-53`. -- **What it does:** a 32-bit linear-congruential generator carried in `s64` +- **What it does:** a 32-bit linear-congruential generator carried in `i64` and masked to 32 bits — `next_u32()`, `next_range(n)` (uniform-ish `[0,n)`), `rng_seeded(seed)`. - **Why general-purpose:** a seedable, reproducible RNG is a textbook stdlib @@ -172,7 +172,7 @@ them, so they are explicitly **not** counted above: ## Summary (top gaps, ranked) -1. **String → number parsing** (`parse_s64` / `parse_f32`) — clean, universally +1. **String → number parsing** (`parse_i64` / `parse_f32`) — clean, universally needed, the missing inverse of the existing formatters. 2. **Seedable PRNG** (`Rng` LCG) — textbook stdlib primitive, entirely absent. 3. **`from_cstr` C-string bridge** (+ public `strlen`) — the most-repeated FFI diff --git a/gem_anim.sx b/gem_anim.sx index 5c22774..078e416 100644 --- a/gem_anim.sx +++ b/gem_anim.sx @@ -38,11 +38,11 @@ IDLE_BOB_A :f32: 0.024; // vertical bob amplitude (cell units) IDLE_RAMP :f32: 0.45; // seconds to ease the idle up from full rest // Smooth per-cell phase: a diagonal gradient wrapped into one breath period. -gem_idle_phase :: (col: s64, row: s64) -> f32 { +gem_idle_phase :: (col: i64, row: i64) -> f32 { cast(f32) ((col * 2 + row * 3) % 8) / 8.0 * TAU } -idle_pose :: (t: f32, col: s64, row: s64) -> GemPose { +idle_pose :: (t: f32, col: i64, row: i64) -> GemPose { ramp := clamp(t / IDLE_RAMP, 0.0, 1.0); w := t / IDLE_PERIOD * TAU + gem_idle_phase(col, row); s := IDLE_SCALE_A * sin(w) * ramp; @@ -135,7 +135,7 @@ GemMotion :: struct { for 0..BOARD_CELLS (i) { self.land_at[i] = -1000.0; } } - stamp_land :: (self: *GemMotion, i: s64) { + stamp_land :: (self: *GemMotion, i: i64) { self.stamp_land_at(i, self.clock); } @@ -143,11 +143,11 @@ GemMotion :: struct { // can BACK-DATE the stamp to when the gem actually touched down mid-fall (each // column lands at a staggered instant): land_squash then resumes the per-round // bounce exactly where render_fall left it, with no double-pop at the seam. - stamp_land_at :: (self: *GemMotion, i: s64, at: f32) { + stamp_land_at :: (self: *GemMotion, i: i64, at: f32) { self.land_at[i] = at; } - land_local :: (self: *GemMotion, i: s64) -> f32 { + land_local :: (self: *GemMotion, i: i64) -> f32 { self.clock - self.land_at[i] } } diff --git a/main.sx b/main.sx index dda3efe..520b61b 100644 --- a/main.sx +++ b/main.sx @@ -117,40 +117,40 @@ build_ui :: () -> View { // be captured without injecting a tap. Absent → normal live behaviour. read_env :: (name: [:0]u8) -> ?string { p := getenv(name); - addr : s64 = xx p; + addr : i64 = xx p; if addr == 0 { return null; } - n := cast(s64) strlen(p); + n := cast(i64) strlen(p); if n == 0 { return ""; } buf := cstring(n); memcpy(buf.ptr, xx p, n); buf } -// Digit arithmetic runs entirely in s64; the result converts to f32 only once at +// Digit arithmetic runs entirely in i64; the result converts to f32 only once at // the end. Doing the digit math in f32 would unify the ASCII literals (45/46/48/ // 57) to f32 across the comparisons, which mis-types the byte compares. parse_f32 :: (s: string) -> f32 { - i : s64 = 0; + i : i64 = 0; neg : bool = false; if s.len > 0 { - c0 : s64 = xx s[0]; + c0 : i64 = xx s[0]; if c0 == 45 { neg = true; i = 1; } // '-' } - intval : s64 = 0; + intval : i64 = 0; while i < s.len { - c : s64 = xx s[i]; + c : i64 = xx s[i]; if c < 48 or c > 57 { break; } intval = intval * 10 + (c - 48); i += 1; } - fracval : s64 = 0; - fracdiv : s64 = 1; + fracval : i64 = 0; + fracdiv : i64 = 1; if i < s.len { - d : s64 = xx s[i]; + d : i64 = xx s[i]; if d == 46 { // '.' i += 1; while i < s.len { - c : s64 = xx s[i]; + c : i64 = xx s[i]; if c < 48 or c > 57 { break; } fracval = fracval * 10 + (c - 48); fracdiv = fracdiv * 10; @@ -163,11 +163,11 @@ parse_f32 :: (s: string) -> f32 { v } -parse_s64 :: (s: string) -> s64 { - i : s64 = 0; - v : s64 = 0; +parse_i64 :: (s: string) -> i64 { + i : i64 = 0; + v : i64 = 0; while i < s.len { - c : s64 = xx s[i]; + c : i64 = xx s[i]; if c < 48 or c > 57 { break; } v = v * 10 + (c - 48); i += 1; @@ -405,7 +405,7 @@ main :: () -> void { g_motion.clock = parse_f32(t); } if sc := read_env("M3TE_SELECT") { - idx := parse_s64(sc); + idx := parse_i64(sc); if idx >= 0 and idx < BOARD_CELLS { g_sel.active = true; g_sel.cell = Cell.{ col = idx % BOARD_COLS, row = idx / BOARD_COLS }; @@ -418,7 +418,7 @@ main :: () -> void { // committed golden stay byte-identical. Purely a render overlay — no board / // score / move / animation state changes and it never gates input. if fp := read_env("M3TE_FPS") { - if parse_s64(fp) != 0 { g_fps_on = true; } + if parse_i64(fp) != 0 { g_fps_on = true; } } // Match-FX capture hook (P11.1). The bursts/popups spawn off a committed move, @@ -433,7 +433,7 @@ main :: () -> void { if fx := read_env("M3TE_FX") { swaps := legal_swaps(g_board); if swaps.len > 0 { - n := parse_s64(fx); + n := parse_i64(fx); if n < 1 { n = 1; } if n > swaps.len { n = swaps.len; } sw := swaps.items[n - 1]; @@ -457,7 +457,7 @@ main :: () -> void { if bs := read_env("M3TE_BADSWAP") { bad := illegal_swaps(g_board); if bad.len > 0 { - n := parse_s64(bs); + n := parse_i64(bs); if n < 1 { n = 1; } if n > bad.len { n = bad.len; } sw := bad.items[n - 1]; @@ -473,10 +473,10 @@ main :: () -> void { // M3TE_MOVE_LIMIT=0 makes it read LOST (budget spent below the goal). With // M3TE_RESTART set non-zero the board is then restart()-ed, capturing the // fresh in_progress board the restart button produces. - if tg := read_env("M3TE_TARGET") { g_board.target_score = parse_s64(tg); } - if ml := read_env("M3TE_MOVE_LIMIT") { g_board.move_limit = parse_s64(ml); } + if tg := read_env("M3TE_TARGET") { g_board.target_score = parse_i64(tg); } + if ml := read_env("M3TE_MOVE_LIMIT") { g_board.move_limit = parse_i64(ml); } if rs := read_env("M3TE_RESTART") { - if parse_s64(rs) != 0 { g_board.restart(BOARD_SEED); } + if parse_i64(rs) != 0 { g_board.restart(BOARD_SEED); } } g_pipeline.set_body(closure(build_ui)); diff --git a/tests/anim_plan.sx b/tests/anim_plan.sx index 97a2e4a..ebcb84b 100644 --- a/tests/anim_plan.sx +++ b/tests/anim_plan.sx @@ -30,8 +30,8 @@ boards_equal :: (x: *Board, y: *Board) -> bool { true } -main :: () -> s32 { - fails : s64 = 0; +main :: () -> i32 { + fails : i64 = 0; // ── Legal swap: plan == model, timeline ends on the model ─────────────── // (5,4)->(6,4): brings R into (5,4), completing R,R,R across cols 3-5 of row diff --git a/tests/arith.sx b/tests/arith.sx index 1505493..b2f3c70 100644 --- a/tests/arith.sx +++ b/tests/arith.sx @@ -4,7 +4,7 @@ #import "modules/std.sx"; t :: #import "test.sx"; -main :: () -> s32 { +main :: () -> i32 { t.expect(2 + 2 == 4, "two plus two is four"); t.expect(7 % 3 == 1, "seven mod three is one"); t.expect(10 - 4 == 6, "ten minus four is six"); diff --git a/tests/banner_layout.sx b/tests/banner_layout.sx index b568b9a..4fff51a 100644 --- a/tests/banner_layout.sx +++ b/tests/banner_layout.sx @@ -19,11 +19,11 @@ irect :: (f: Frame) -> string { format("({},{},{},{})", - cast(s64) f.origin.x, cast(s64) f.origin.y, - cast(s64) f.size.width, cast(s64) f.size.height) + cast(i64) f.origin.x, cast(i64) f.origin.y, + cast(i64) f.size.width, cast(i64) f.size.height) } -main :: () -> s32 { +main :: () -> i32 { // 800×600, no safe inset → 600px square grid, cell 75, origin (100,0): the // same layout tests/hit_test.sx pins, so the numbers are checkable by hand. lay : BoardLayout = ---; @@ -36,18 +36,18 @@ main :: () -> s32 { print("title {}\n", irect(bl.title)); print("button {}\n", irect(bl.button)); - fails : s64 = 0; + fails : i64 = 0; // The button is horizontally centered on the grid (centred banner). bcx := bl.button.mid_x(); - if cast(s64) bcx != cast(s64) grid.mid_x() { fails += 1; } - print("button mid_x {} grid mid_x {}\n", cast(s64) bcx, cast(s64) grid.mid_x()); + if cast(i64) bcx != cast(i64) grid.mid_x() { fails += 1; } + print("button mid_x {} grid mid_x {}\n", cast(i64) bcx, cast(i64) grid.mid_x()); // The whole button sits inside the panel — its four corners are contained, // so it can never spill outside the drawn card. bx0 := bl.button.origin.x; by0 := bl.button.origin.y; bx1 := bl.button.max_x(); by1 := bl.button.max_y(); - corners_in : s64 = 0; + corners_in : i64 = 0; if bl.panel.contains(Point.{ x = bx0, y = by0 }) { corners_in += 1; } if bl.panel.contains(Point.{ x = bx1, y = by0 }) { corners_in += 1; } if bl.panel.contains(Point.{ x = bx0, y = by1 }) { corners_in += 1; } @@ -67,7 +67,7 @@ main :: () -> s32 { // in the button, so each leaves the level frozen. corner_cell := Point.{ x = grid.origin.x + lay.cell_size * 0.5, y = grid.origin.y + lay.cell_size * 0.5 }; outside := Point.{ x = bl.panel.origin.x - 5.0, y = bl.panel.mid_y() }; - off_hits : s64 = 0; + off_hits : i64 = 0; if bl.button.contains(corner_cell) { off_hits += 1; } if bl.button.contains(outside) { off_hits += 1; } if off_hits != 0 { fails += 1; } diff --git a/tests/board_init.sx b/tests/board_init.sx index 9b4b829..9ad26ad 100644 --- a/tests/board_init.sx +++ b/tests/board_init.sx @@ -10,8 +10,8 @@ SEED :: 1337; // Count every horizontal or vertical window of three consecutive same-type // gems. A correctly initialized board has zero. This walks the finished board // independently of the placement logic, so it's a real check, not a tautology. -count_three_runs :: (b: *Board) -> s32 { - runs : s32 = 0; +count_three_runs :: (b: *Board) -> i32 { + runs : i32 = 0; for 0..BOARD_ROWS (row) { for 0..(BOARD_COLS - 2) (col) { g := b.at(col, row); @@ -27,7 +27,7 @@ count_three_runs :: (b: *Board) -> s32 { runs } -main :: () -> s32 { +main :: () -> i32 { board : Board = ---; board.init(SEED); diff --git a/tests/cascade.sx b/tests/cascade.sx index 6177758..3fa9b60 100644 --- a/tests/cascade.sx +++ b/tests/cascade.sx @@ -84,7 +84,7 @@ checker_board :: () -> Board { b } -main :: () -> s32 { +main :: () -> i32 { print("== cascade (resolution loop) ==\n"); // Drive the loop one round at a time so each post-round board is visible in @@ -94,7 +94,7 @@ main :: () -> s32 { out(board_dump(@b)); depth := 0; - counts := List(s64).{}; + counts := List(i64).{}; while true { n := resolve_step(@b); if n == 0 { break; } diff --git a/tests/cascade_cue.sx b/tests/cascade_cue.sx index 853e179..46e63de 100644 --- a/tests/cascade_cue.sx +++ b/tests/cascade_cue.sx @@ -8,13 +8,13 @@ #import "modules/std.sx"; #import "audio.sx"; -main :: () -> s32 { +main :: () -> i32 { print("== cascade cue selection (depth -> combo cue) ==\n"); // Walk a representative depth range (0..9) so both clamps and the monotonic // middle are visible: depths 0,1 pin to the first cue; depths >= 5 pin to // the last; 2,3,4 step up one cue at a time. - prev : s64 = -1; + prev : i64 = -1; for 0..10 (depth) { idx := cascade_cue_index(depth); print("depth {} -> idx {} ({})\n", depth, idx, cascade_cue_name(idx)); diff --git a/tests/cascade_rounds.sx b/tests/cascade_rounds.sx index decf440..182ff86 100644 --- a/tests/cascade_rounds.sx +++ b/tests/cascade_rounds.sx @@ -13,7 +13,7 @@ #import "board_anim.sx"; #import "audio.sx"; -main :: () -> s32 { +main :: () -> i32 { print("== per-round cascade cue timing ==\n"); // `cascade_rounds_started` = how many cascade rounds have BEGUN clearing by @@ -21,7 +21,7 @@ main :: () -> s32 { // Round k (0-based) starts clearing at 0.16 + k*0.36; sampled safely INSIDE // each round window so the integer step is unambiguous. Locked for 5 rounds. print("-- started-count across a 5-round chain --\n"); - rounds : s64 = 5; + rounds : i64 = 5; print("e=0.00 -> {}\n", cascade_rounds_started(0.00, rounds)); print("e=0.10 -> {}\n", cascade_rounds_started(0.10, rounds)); print("e=0.20 -> {}\n", cascade_rounds_started(0.20, rounds)); @@ -37,7 +37,7 @@ main :: () -> s32 { // ascending. This IS the loop main's frame loop runs; the emitted run is the // locked acceptance ordering. print("-- ascending per-round run --\n"); - fired : s64 = 0; + fired : i64 = 0; elapsed : f32 = 0.0; while fired < rounds { started := cascade_rounds_started(elapsed, rounds); diff --git a/tests/clear.sx b/tests/clear.sx index b229035..fb7d9ac 100644 --- a/tests/clear.sx +++ b/tests/clear.sx @@ -38,7 +38,7 @@ load_board :: (rows: []string) -> Board { // Detect→clear one scene, snapshot before/after, and assert the three clear // invariants against the matched-cell set: every flagged cell is now a hole, // every unflagged cell is unchanged, and the returned count is exact. -scene :: (name: string, rows: []string, want_cleared: s64) { +scene :: (name: string, rows: []string, want_cleared: i64) { b := load_board(rows); orig := load_board(rows); // pristine copy for the unchanged check @@ -65,7 +65,7 @@ scene :: (name: string, rows: []string, want_cleared: s64) { t.expect(cleared == want_cleared, concat(name, ": cleared count exact")); } -main :: () -> s32 { +main :: () -> i32 { print("== clear (detect -> clear) ==\n"); // Single horizontal 3-run (row 3, cols 2-4) → three holes there only. diff --git a/tests/collapse.sx b/tests/collapse.sx index 934d7e1..ebd1f61 100644 --- a/tests/collapse.sx +++ b/tests/collapse.sx @@ -79,7 +79,7 @@ scene :: (name: string, rows: []string, want_moved: bool) { t.expect(moved == want_moved, concat(name, ": moved flag exact")); } -main :: () -> s32 { +main :: () -> i32 { print("== collapse (gravity) ==\n"); // Eight independent columns, one case each (top-to-bottom): diff --git a/tests/combo.sx b/tests/combo.sx index f902dcc..da3341e 100644 --- a/tests/combo.sx +++ b/tests/combo.sx @@ -31,7 +31,7 @@ char_to_gem :: (c: u8) -> Gem { // Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars), // seeded RNG, running score zeroed so `board.score` ends equal to the payout. -load_board :: (rows: []string, seed: s64) -> Board { +load_board :: (rows: []string, seed: i64) -> Board { b : Board = ---; for 0..BOARD_ROWS (row) { line := rows[row]; @@ -51,14 +51,14 @@ load_board :: (rows: []string, seed: s64) -> Board { // identical board awards `want_mult` into `Board.score` and reports it as // `Cascade.awarded` at the same depth. A depth-1 settle must equal the flat sum // (no bonus); a deeper chain must strictly exceed it. -scene :: (name: string, rows: []string, seed: s64, want_flat: s64, want_mult: s64) { +scene :: (name: string, rows: []string, seed: i64, want_flat: i64, want_mult: i64) { print("== {} ==\n", name); b := load_board(rows, seed); out(board_dump(@b)); - flat : s64 = 0; - mult : s64 = 0; - depth : s64 = 0; + flat : i64 = 0; + mult : i64 = 0; + depth : i64 = 0; while true { base := score_round(@b); n := resolve_step(@b); @@ -90,7 +90,7 @@ scene :: (name: string, rows: []string, seed: s64, want_flat: s64, want_mult: s6 t.expect(b2.score == want_mult, concat(name, ": resolve accumulates into board.score")); } -main :: () -> s32 { +main :: () -> i32 { print("== combo (cascade multiplier) ==\n"); // Single-round clear (seed 0): one RRR clears and the refill makes no new diff --git a/tests/easing.sx b/tests/easing.sx index 3289556..31b1749 100644 --- a/tests/easing.sx +++ b/tests/easing.sx @@ -19,8 +19,8 @@ fabs :: (x: f32) -> f32 { if x < 0.0 then 0.0 - x else x } approx :: (a: f32, b: f32) -> bool { fabs(a - b) < 0.0001 } -main :: () -> s32 { - fails : s64 = 0; +main :: () -> i32 { + fails : i64 = 0; // 1. Endpoints are locked: every curve starts/ends exactly on its rest value // (the in/out curves at 1, the spring at 1, the squash envelope at 0). diff --git a/tests/fx_combo.sx b/tests/fx_combo.sx index 7ec64df..f9b8f63 100644 --- a/tests/fx_combo.sx +++ b/tests/fx_combo.sx @@ -13,14 +13,14 @@ #import "modules/std.sx"; #import "board_fx.sx"; -main :: () -> s32 { +main :: () -> i32 { print("== combo emphasis selection (depth -> fx level / popup font) ==\n"); // The cascade-cue index per depth 0..9, copied from cascade_cue.stdout. The // FX level must equal this entry for entry — the audio/visual lockstep. - expect_level : [10]s64 = .{ 0, 0, 1, 2, 3, 4, 4, 4, 4, 4 }; + expect_level : [10]i64 = .{ 0, 0, 1, 2, 3, 4, 4, 4, 4, 4 }; - prev : s64 = -1; + prev : i64 = -1; for 0..10 (depth) { lvl := fx_combo_level(depth); font := fx_popup_font(depth); diff --git a/tests/gem_pose.sx b/tests/gem_pose.sx index 70db336..b019043 100644 --- a/tests/gem_pose.sx +++ b/tests/gem_pose.sx @@ -18,8 +18,8 @@ fabs :: (x: f32) -> f32 { if x < 0.0 then 0.0 - x else x } approx :: (a: f32, b: f32) -> bool { fabs(a - b) < 0.0001 } -main :: () -> s32 { - fails : s64 = 0; +main :: () -> i32 { + fails : i64 = 0; // 1. t==0 idle pose is EXACTLY rest for every cell (the determinism invariant). print("== idle t=0 is rest for all cells ==\n"); diff --git a/tests/hit_test.sx b/tests/hit_test.sx index b2887df..327b0ef 100644 --- a/tests/hit_test.sx +++ b/tests/hit_test.sx @@ -14,19 +14,19 @@ #import "board.sx"; #import "board_layout.sx"; -main :: () -> s32 { +main :: () -> i32 { // 800×600 with no safe inset → a 600px square grid, cell 75, centered: the // grid origin lands at (100, 0). Integer math keeps the dump deterministic. lay : BoardLayout = ---; lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero()); print("grid origin ({},{}) cell {}\n", - cast(s64) lay.origin.x, cast(s64) lay.origin.y, cast(s64) lay.cell_size); + cast(i64) lay.origin.x, cast(i64) lay.origin.y, cast(i64) lay.cell_size); - fails : s64 = 0; + fails : i64 = 0; // Every cell center must map back to its own cell. - hits : s64 = 0; + hits : i64 = 0; for 0..BOARD_ROWS (row) { for 0..BOARD_COLS (col) { cf := lay.cell_frame(col, row); @@ -42,8 +42,8 @@ main :: () -> s32 { // A cell's top-left corner belongs to that cell (the leading edge is // inclusive), so corner-of-(3,5) resolves to (3,5). corner := Point.{ x = lay.origin.x + 3.0 * lay.cell_size, y = lay.origin.y + 5.0 * lay.cell_size }; - corner_col : s64 = -1; - corner_row : s64 = -1; + corner_col : i64 = -1; + corner_row : i64 = -1; if h := lay.point_to_cell(corner) { corner_col = h.col; corner_row = h.row; } if corner_col != 3 or corner_row != 5 { fails += 1; } print("corner maps to ({},{})\n", corner_col, corner_row); @@ -53,7 +53,7 @@ main :: () -> s32 { off_left := Point.{ x = lay.origin.x - 5.0, y = lay.origin.y + 10.0 }; off_above := Point.{ x = lay.origin.x + 10.0, y = lay.origin.y - 5.0 }; off_right := Point.{ x = lay.origin.x + 8.0 * lay.cell_size + 1.0, y = lay.origin.y + 10.0 }; - on_board : s64 = 0; + on_board : i64 = 0; if h := lay.point_to_cell(off_left) { on_board += 1; print("off_left hit ({},{})\n", h.col, h.row); } if h := lay.point_to_cell(off_above) { on_board += 1; print("off_above hit ({},{})\n", h.col, h.row); } if h := lay.point_to_cell(off_right) { on_board += 1; print("off_right hit ({},{})\n", h.col, h.row); } diff --git a/tests/level.sx b/tests/level.sx index fc1a4aa..a0b3790 100644 --- a/tests/level.sx +++ b/tests/level.sx @@ -34,7 +34,7 @@ char_to_gem :: (c: u8) -> Gem { // Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars), // seeded RNG, running score zeroed, the turn counters reset to a fresh game, and // the per-level goal set. -load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) -> Board { +load_board :: (rows: []string, seed: i64, move_limit: i64, target_score: i64) -> Board { b : Board = ---; for 0..BOARD_ROWS (row) { line := rows[row]; @@ -55,7 +55,7 @@ boards_equal :: (a: *Board, b: *Board) -> bool { true } -main :: () -> s32 { +main :: () -> i32 { print("== level (turn / goal state machine) ==\n"); // ── Start: a fresh seeded board reads in_progress with the default goal ── diff --git a/tests/match_detect.sx b/tests/match_detect.sx index 573a4d8..bcc888a 100644 --- a/tests/match_detect.sx +++ b/tests/match_detect.sx @@ -20,7 +20,7 @@ char_to_gem :: (c: u8) -> Gem { // Build a scene: load an 8x8 board from `rows` (top row first, each exactly // BOARD_COLS gem characters), detect matches, print board + matched dump, and // assert the matched-cell count. -scene :: (name: string, rows: []string, want_count: s64) { +scene :: (name: string, rows: []string, want_count: i64) { b : Board = ---; for 0..BOARD_ROWS (row) { line := rows[row]; @@ -38,7 +38,7 @@ scene :: (name: string, rows: []string, want_count: s64) { t.expect(m.count() == want_count, name); } -main :: () -> s32 { +main :: () -> i32 { // Single horizontal 3-run (row 3, cols 2-4). scene("horizontal-3", .[ "OGOGOGOG", diff --git a/tests/refill.sx b/tests/refill.sx index de3022c..75fcfef 100644 --- a/tests/refill.sx +++ b/tests/refill.sx @@ -41,8 +41,8 @@ load_board :: (rows: []string) -> Board { b } -count_empties :: (b: *Board) -> s64 { - n : s64 = 0; +count_empties :: (b: *Board) -> i64 { + n : i64 = 0; for 0..BOARD_CELLS (i) { if b.cells[i] == .empty { n += 1; } } n } @@ -72,7 +72,7 @@ fresh_board :: () -> Board { b } -main :: () -> s32 { +main :: () -> i32 { print("== refill (seeded) ==\n"); // Pipeline, snapshotting each stage. @@ -123,7 +123,7 @@ main :: () -> s32 { // filled, then refill again. The board's RNG has advanced past the first // fill, so the second fill draws new gems — proof it does NOT reseed per call. holes_n := 0; - hole_idx : [BOARD_CELLS]s64 = ---; + hole_idx : [BOARD_CELLS]i64 = ---; fill1 : [BOARD_CELLS]Gem = ---; for 0..BOARD_CELLS (i) { if pre.cells[i] == .empty { diff --git a/tests/score.sx b/tests/score.sx index 0c956da..e0e7427 100644 --- a/tests/score.sx +++ b/tests/score.sx @@ -42,7 +42,7 @@ load_board :: (rows: []string) -> Board { // Score one scene: snapshot board + enumerated runs + points, then assert // `score_round` is exact and `add_round_score` accumulates it into `board.score`. -scene :: (name: string, rows: []string, want_points: s64) { +scene :: (name: string, rows: []string, want_points: i64) { b := load_board(rows); runs := find_runs(@b); @@ -58,7 +58,7 @@ scene :: (name: string, rows: []string, want_points: s64) { concat(name, ": add_round_score accumulates into board.score")); } -main :: () -> s32 { +main :: () -> i32 { print("== score (base match scoring) ==\n"); // Single length-3 horizontal run (row 3, cols 2-4) -> SCORE_RUN_3 = 30. diff --git a/tests/swap_legality.sx b/tests/swap_legality.sx index 5548031..1cd94da 100644 --- a/tests/swap_legality.sx +++ b/tests/swap_legality.sx @@ -44,11 +44,11 @@ boards_equal :: (x: *Board, y: *Board) -> bool { true } -cell :: (col: s64, row: s64) -> Cell { +cell :: (col: i64, row: i64) -> Cell { Cell.{ col = col, row = row } } -main :: () -> s32 { +main :: () -> i32 { print("== swap & legality ==\n"); // Board whose ONLY swap-formable match is the adjacent (2,3)<->(3,3) diff --git a/tests/swipe_commit.sx b/tests/swipe_commit.sx index 19154d3..eb8aa47 100644 --- a/tests/swipe_commit.sx +++ b/tests/swipe_commit.sx @@ -20,7 +20,7 @@ SEED :: 1337; -cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point { +cell_center :: (lay: *BoardLayout, col: i64, row: i64) -> Point { cf := lay.cell_frame(col, row); Point.{ x = cf.mid_x(), y = cf.mid_y() } } @@ -32,14 +32,14 @@ boards_equal :: (x: *Board, y: *Board) -> bool { true } -main :: () -> s32 { +main :: () -> i32 { // 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0). A // 60px drag clears the cell*0.5 = 37.5px swipe threshold on the dominant axis. lay : BoardLayout = ---; lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero()); D : f32 = 60.0; - fails : s64 = 0; + fails : i64 = 0; // ── ILLEGAL swipe reverts ────────────────────────────────────────────── // (0,0) and (1,0) are both red on the seed board, so swapping them forms no diff --git a/tests/swipe_intent.sx b/tests/swipe_intent.sx index f83f423..faf19c6 100644 --- a/tests/swipe_intent.sx +++ b/tests/swipe_intent.sx @@ -14,14 +14,14 @@ #import "board_layout.sx"; #import "swipe.sx"; -cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point { +cell_center :: (lay: *BoardLayout, col: i64, row: i64) -> Point { cf := lay.cell_frame(col, row); Point.{ x = cf.mid_x(), y = cf.mid_y() } } // Print the resolved intent (locked in the golden) and report whether it matches // the expected adjacent pair (A, B). Drives the exit code alongside the dump. -expect_swap :: (label: string, got: ?Swap, ac: s64, ar: s64, bc: s64, br: s64) -> bool { +expect_swap :: (label: string, got: ?Swap, ac: i64, ar: i64, bc: i64, br: i64) -> bool { if s := got { print("{}: ({},{})->({},{})\n", label, s.a.col, s.a.row, s.b.col, s.b.row); return s.a.col == ac and s.a.row == ar and s.b.col == bc and s.b.row == br; @@ -39,14 +39,14 @@ expect_none :: (label: string, got: ?Swap) -> bool { true } -main :: () -> s32 { +main :: () -> i32 { lay : BoardLayout = ---; lay.compute(Frame.make(0.0, 0.0, 800.0, 600.0), EdgeInsets.zero()); print("grid origin ({},{}) cell {} threshold {}\n", - cast(s64) lay.origin.x, cast(s64) lay.origin.y, cast(s64) lay.cell_size, - cast(s64) (lay.cell_size * SWIPE_THRESHOLD_FRACTION)); + cast(i64) lay.origin.x, cast(i64) lay.origin.y, cast(i64) lay.cell_size, + cast(i64) (lay.cell_size * SWIPE_THRESHOLD_FRACTION)); - fails : s64 = 0; + fails : i64 = 0; // A known interior cell; every cardinal swipe from it stays on the board. start := cell_center(@lay, 3, 5); diff --git a/tests/swipe_reshuffle.sx b/tests/swipe_reshuffle.sx index 0834634..975cfa7 100644 --- a/tests/swipe_reshuffle.sx +++ b/tests/swipe_reshuffle.sx @@ -36,7 +36,7 @@ char_to_gem :: (c: u8) -> Gem { // Load an 8x8 board from `rows` (top row first), seeded RNG, score zeroed, turn // counters reset to a fresh game, and the per-level goal set. -load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) -> Board { +load_board :: (rows: []string, seed: i64, move_limit: i64, target_score: i64) -> Board { b : Board = ---; for 0..BOARD_ROWS (row) { line := rows[row]; @@ -52,13 +52,13 @@ load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) -> b } -cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point { +cell_center :: (lay: *BoardLayout, col: i64, row: i64) -> Point { cf := lay.cell_frame(col, row); Point.{ x = cf.mid_x(), y = cf.mid_y() } } -main :: () -> s32 { - fails : s64 = 0; +main :: () -> i32 { + fails : i64 = 0; // 800×600, no safe inset → 600px square grid, cell 75, origin (100, 0). A 60px // drag clears the cell*0.5 = 37.5px swipe threshold on the dominant axis — the diff --git a/tests/turn.sx b/tests/turn.sx index 0db53ee..d6d25bf 100644 --- a/tests/turn.sx +++ b/tests/turn.sx @@ -37,7 +37,7 @@ char_to_gem :: (c: u8) -> Gem { // Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars), // seeded RNG, running score zeroed, and the turn counters reset to a fresh game // (no moves made, the given move budget). -load_board :: (rows: []string, seed: s64, move_limit: s64) -> Board { +load_board :: (rows: []string, seed: i64, move_limit: i64) -> Board { b : Board = ---; for 0..BOARD_ROWS (row) { line := rows[row]; @@ -60,7 +60,7 @@ boards_equal :: (a: *Board, b: *Board) -> bool { // One flag scene: snapshot the board, then count its single round's special // runs and assert the tallies (and the boolean flags derived from them) are // exactly the documented values. No RNG, no clear — pure detection. -flag_scene :: (name: string, rows: []string, want_len4: s64, want_len5_plus: s64) { +flag_scene :: (name: string, rows: []string, want_len4: i64, want_len5_plus: i64) { print("== {} ==\n", name); b := load_board(rows, 0, LIMIT); out(board_dump(@b)); @@ -71,7 +71,7 @@ flag_scene :: (name: string, rows: []string, want_len4: s64, want_len5_plus: s64 t.expect(sp.len5_plus == want_len5_plus, concat(name, ": len5_plus count exact")); } -main :: () -> s32 { +main :: () -> i32 { print("== turn (accounting + special-match flagging) ==\n"); // ── Special-match flagging (single round, no RNG) ────────────────────── diff --git a/tools/key_particle.sx b/tools/key_particle.sx index 459c470..4bd5565 100644 --- a/tools/key_particle.sx +++ b/tools/key_particle.sx @@ -33,14 +33,14 @@ GRAY_TOL :: 24; // max channel spread still considered neutral gray LUM_MARGIN :: 4; // lum headroom above the light checker shade is_gray :: (r: u8, g: u8, b: u8) -> bool { - hi := max(max(cast(s64) r, cast(s64) g), cast(s64) b); - lo := min(min(cast(s64) r, cast(s64) g), cast(s64) b); + hi := max(max(cast(i64) r, cast(i64) g), cast(i64) b); + lo := min(min(cast(i64) r, cast(i64) g), cast(i64) b); hi - lo <= GRAY_TOL } // Mark pixel `i` as removed background and queue it, if it is unvisited checker // (near-neutral gray no brighter than the light checker shade + margin). -fd_seed :: (i: s64, bg: [*]u8, lum: [*]s64, src: [*]u8, stack: [*]s64, sp: *s64, lim: s64) { +fd_seed :: (i: i64, bg: [*]u8, lum: [*]i64, src: [*]u8, stack: [*]i64, sp: *i64, lim: i64) { if bg[i] != 0 { return; } p := i * 4; if lum[i] <= lim and is_gray(src[p], src[p+1], src[p+2]) { @@ -50,35 +50,35 @@ fd_seed :: (i: s64, bg: [*]u8, lum: [*]s64, src: [*]u8, stack: [*]s64, sp: *s64, } } -main :: () -> s32 { - w : s32 = 0; - h : s32 = 0; - ch : s32 = 0; +main :: () -> i32 { + w : i32 = 0; + h : i32 = 0; + ch : i32 = 0; src : [*]u8 = xx stbi_load(SRC_PATH, @w, @h, @ch, 4); if xx src == 0 { print("FATAL: could not load {}\n", SRC_PATH); return 1; } - W := cast(s64) w; - H := cast(s64) h; + W := cast(i64) w; + H := cast(i64) h; N := W * H; print("loaded {}x{} ({} src channels)\n", w, h, ch); // Hoisted working locals (see codegen note above). - y : s64 = 0; - x : s64 = 0; - i : s64 = 0; - p : s64 = 0; - r : s64 = 0; - g : s64 = 0; - b : s64 = 0; - l : s64 = 0; + y : i64 = 0; + x : i64 = 0; + i : i64 = 0; + p : i64 = 0; + r : i64 = 0; + g : i64 = 0; + b : i64 = 0; + l : i64 = 0; // Per-pixel luminance, plus the checker shades read off the border ring // (the border is pure checker — the glow never reaches the corners). - lum : [*]s64 = xx context.allocator.alloc_bytes(N * size_of(s64)); - c_lo : s64 = 255; - c_hi : s64 = 0; + lum : [*]i64 = xx context.allocator.alloc_bytes(N * size_of(i64)); + c_lo : i64 = 255; + c_hi : i64 = 0; y = 0; while y < H { x = 0; @@ -104,8 +104,8 @@ main :: () -> s32 { // border pixel. `bg[i]==1` marks a removed (transparent) background pixel. bg : [*]u8 = xx context.allocator.alloc_bytes(N); memset(xx bg, 0, N); - stack : [*]s64 = xx context.allocator.alloc_bytes(N * size_of(s64)); - sp : s64 = 0; + stack : [*]i64 = xx context.allocator.alloc_bytes(N * size_of(i64)); + sp : i64 = 0; checker_lim := c_hi + LUM_MARGIN; x = 0; @@ -121,12 +121,12 @@ main :: () -> s32 { y += 1; } - cx : s64 = 0; - cy : s64 = 0; - dx : s64 = 0; - dy : s64 = 0; - nx : s64 = 0; - ny : s64 = 0; + cx : i64 = 0; + cy : i64 = 0; + dx : i64 = 0; + dy : i64 = 0; + nx : i64 = 0; + ny : i64 = 0; while sp > 0 { sp -= 1; i = stack[sp]; @@ -154,8 +154,8 @@ main :: () -> s32 { denom := cast(f32) (255 - c_hi); if denom < 1.0 { denom = 1.0; } alpha : [*]f32 = xx context.allocator.alloc_bytes(N * size_of(f32)); - kept : s64 = 0; - n_bg : s64 = 0; + kept : i64 = 0; + n_bg : i64 = 0; a : f32 = 0.0; i = 0; while i < N { @@ -178,26 +178,26 @@ main :: () -> s32 { sxf := cast(f32) W / cast(f32) OUT_DIM; syf := cast(f32) H / cast(f32) OUT_DIM; max_a : f32 = 0.0; - ty : s64 = 0; - tx : s64 = 0; - x0 : s64 = 0; - x1 : s64 = 0; - y0 : s64 = 0; - y1 : s64 = 0; + ty : i64 = 0; + tx : i64 = 0; + x0 : i64 = 0; + x1 : i64 = 0; + y0 : i64 = 0; + y1 : i64 = 0; sum : f32 = 0.0; - cnt : s64 = 0; - sy : s64 = 0; - sx : s64 = 0; + cnt : i64 = 0; + sy : i64 = 0; + sx : i64 = 0; av : f32 = 0.0; - o : s64 = 0; + o : i64 = 0; ty = 0; while ty < OUT_DIM { tx = 0; while tx < OUT_DIM { - x0 = cast(s64) (cast(f32) tx * sxf); - x1 = cast(s64) (cast(f32) (tx + 1) * sxf); - y0 = cast(s64) (cast(f32) ty * syf); - y1 = cast(s64) (cast(f32) (ty + 1) * syf); + x0 = cast(i64) (cast(f32) tx * sxf); + x1 = cast(i64) (cast(f32) (tx + 1) * sxf); + y0 = cast(i64) (cast(f32) ty * syf); + y1 = cast(i64) (cast(f32) (ty + 1) * syf); if x1 <= x0 { x1 = x0 + 1; } if y1 <= y0 { y1 = y0 + 1; } sum = 0.0;