Sand Blocks: Drop Puzzle — Buyer Guide

Unity 6000.0.62f1 · Universal Render Pipeline · LocalQA handoff candidate

Overview

Sand Blocks is a portrait drag puzzle with 500 levels. Glass tanks at the top of the board hold coloured sand in layers; every tank column pours its bottom layer straight down into the box that rests under it. Drag the open boxes on the board (they slide through free cells only) under a column whose bottom sand matches their colour: the sand pours in, the full box pops and frees its cells. Fill every box before the countdown reaches 00:00 to win; at 00:00 the level is lost. Level 1 is a guided tutorial with the shared tutorial hand.

Mechanics introduced over the campaign: layered tank columns (level 5), numbered covers that open after that many boxes have been filled (level 10), boxes hidden under closed covers (level 15), key boxes that open the padlocked box (level 40) and pipe-fed tanks (level 60), on nine board families (twin tanks, corridors, steps, pillars, channels, covered boards, towers, pockets and vaults). Every 50th level is a SUPER HARD level. Levels 1 and 200 are the two recorded boards of the original game.

The HyperCasualLiveOpsKit (separate root Assets/HyperCasualLiveOpsKit, with its own guide) supplies the menu, level selection, capsule HUD, results, wallet, quests, pass, minigames, shop (LocalQA dummy store), ads (LocalQA mock) and the booster bar with refills.

Getting Started

  1. Create a Unity 6000.0.62f1 project with the Universal Render Pipeline, the Input System (set Active Input Handling to Both or Input System) and uGUI; import TextMesh Pro Essential Resources when Unity asks.
  2. Import the package. Assign Assets/Game/ProjectRenderSettings/PC_RPAsset.asset (or Mobile_RPAsset.asset) in Graphics and Quality settings and use the Linear colour space.
  3. Add Assets/HyperCasualLiveOpsKit/StarterMenu/Scenes/HLK_MainMenu.unity (first) and Assets/Game/Scenes/Game.unity to the build settings and press Play in the menu scene. Choose a level or press PLAY NEXT LEVEL.
  4. Open Tools → Sand Blocks → Level Editor or Auto Level Generator to inspect and change the campaign, and Tools → Sand Blocks → Documentation for this guide.

Folder Structure

FolderContents
Game/Scripts/CorePure C# rules: LevelData (the level JSON contract), Sim (fixed 60 Hz simulation of dragging, pouring, completion, covers, keys/padlock, countdown), SimBoosters (freeze, hammer, UFO), Contract (recorded-level contracts).
Game/Scripts/Core/CampaignThe generator: CampaignPlan (introductions, family schedule, per-level request), Families (board layouts), LevelBuilder (forward construction with a witness), CampaignGenerator, Witness (WitnessMove, WitnessReplay), Solver (independent policy), Footprints, LevelJson.
Game/Scripts/GamePresentation: GameController (input, level start, host hooks), Viewport (camera and host framing), WorldView, BoardView, PieceView, SandStreamView, FxView, BoosterView, AudioView, Session (level catalogue).
Game/Scripts/UIThe game's own HUD (countdown plate, SUPER HARD banner and the standalone menu/result screens).
Game/Scripts/ModelsThe model registry and procedural/native model variants used by the board and boxes.
Game/IntegrationThe link to the kit: SandBlocksHlkBridge (level selection, pointer/modal gating, one settlement per attempt, results, analytics, play area), SandBlocksPowerUpAdapter (freeze/hammer/UFO on the kit bar), SandBlocksBoosterProgression, SandBlocksPointerDriver, booster definitions in PowerUps.
Game/LevelTools, Game/ProductionLevelToolsEditor-only level editor and generator (Sand Blocks adapter on the shared level-tools framework) and its baseline data.
Game/Resources/LevelsThe playable campaign Level_1.json … Level_500.json and CampaignAuthoring.json (authoring data, never counted as a level).
Game/Resources/LevelSourcesThe two recorded boards level-1.json and level-200.json as originally authored.
Game/Resources (other)Native and generated models, materials, shaders, images, audio, Unity-provided default fonts and the booster progression policy (HLK/BoosterProgression.json).
Game/Scenes, Game/ProjectRenderSettingsThe gameplay scene and the URP assets.

Gameplay

Moving. Press a visible box and drag: it follows the finger through free cells (it collides with other boxes, walls, closed covers and the board edge) and snaps to the nearest cell on release. One box is held at a time. A box under a closed cover cannot be picked up; a press on it counts as a blocked attempt.

Pouring. Each tank column pours its bottom sand layer into the box on its dock cell (the cell below the column; the outermost columns also feed the cell beside the tank's bottom row). The colour must match. A box fills at the level's fillPctPerSec, split over the columns feeding it, and the sand leaves those columns' queues; when a layer runs out the next colour above it reaches the bottom.

Covers, keys, padlock. A cover shows a number: every filled box lowers it by one; at zero the cover rolls away and the boxes under it can be moved. Filling a key box sends its key to the padlocked box; when all keys have arrived, the padlock opens and that box can receive sand.

Outcome. Filling every box wins (the kit results credit the win reward once, default 50 coins). The countdown (90–240 s per level) reaching 00:00 loses; the lose card offers TRY AGAIN. Every campaign level has a stored solution (witness) that wins in the simulation with at least 61 s to spare.

Scripts Reference

TypeRole
SandBlocks.Core.LevelDataParses the level JSON (FromJson, Validate); LevelJson.ToJson writes it.
SandBlocks.Core.SimDeterministic simulation: PointerDown/Move/Up, Step, Legal, Hidden, events (grab, drop, complete, unlock, cover-open, won, lost) and booster effects (UseBooster, CanTarget).
SandBlocks.Core.WitnessMove, WitnessReplay, SolverThe stored solution (a list of box drags with their cell paths), its proof through the Sim (Play) and an independent search policy (Solve).
SandBlocks.Core.CampaignGenerator, CampaignPlanThe saved progression plan (introductions, family schedule, recovery and SUPER HARD levels) and Generate(index, family, seed, …); every slot is reproducible on its own.
SandBlocks.Game.GameControllerStarts levels (StartLevel, Restart), turns pointer input into Sim input, exposes LevelStarted, OnSimEvent, PressRefused and the host hooks.
SandBlocks.Game.ViewportCamera. Under the kit the board is fitted full-window into HostPlayArea (SolveHost, FrameHost).
SandBlocks.Integration.SandBlocksHlkBridgeKit link: selected level, one settlement per attempt, results, sandblocks_level_end analytics (level, won, coins, moves, fills, blocked, seconds), PlayArea().

Level Editor

Tools → Sand Blocks → Level Editor lists the campaign on the left (duplicate, delete, move up/down, Undo). The canvas draws the board as the game lays it out: tanks with their sand layers (the bottom layer pours first), dock marks under each tank column, board cells, walls, numbered covers and every box in its colour and footprint with key/padlock marks.

Every edit clears the level's stored witness. Saving refuses levels without a proven witness, invalid layouts and duplicate puzzles, and leaves the published JSON unchanged on failure. Play saved level opens it in the game scene.

Automatic Level Generator

Tools → Sand Blocks → Auto Level Generator (same window) saves the generation request (final count, seed, attempts, minimum formations, repeat streak, append) and generates into the editor for review before saving. Generation is deterministic per seed and slot: each board is built forward from its solution — the generator lays out the family's tanks and board, places the boxes, then plays the planned delivery order to derive which sand each column holds, so every board is solvable by construction and its witness is replayed on the Sim. Slots 1 and 200 keep the recorded boards. Append mode keeps the existing levels and adds new ones after them; cancelling leaves the campaign unchanged. A 500-level run takes about three minutes in the editor.

Boosters

BoosterUnlockEffect
FreezeLevel 2Stops the countdown for 10 s.
HammerLevel 7Tap a visible box: it fills at once; its sand is taken from the tanks.
UFOLevel 12Tap a visible box: every box of that colour fills and that colour's sand leaves the tanks.

Each unlock grants two free charges once. Refills cost 100 + 25 × floor((highest level − 1) / 10) coins or a rewarded ad (LocalQA mock). The policy is Game/Resources/HLK/BoosterProgression.json. The first use shows the booster explanation; a targeted booster then guides its first tap with the shared tutorial hand on a valid box. Taps on covered boxes are not targets and cost nothing.

Level Variation

Families: twin tanks (from level 2), corridor (3), step (5), pillar (7), channels (10), covered boards (15), towers (20), pocket (25) and vault (40). The opening 2–40 introduces one idea at a time; afterwards a seeded schedule never shows a family three times running. Boxes grow from 2–6 per board in levels 1–10 to about 12 on average late in the campaign (up to 28), colours from 2 to 9. Levels ending in 8 (from 28) are lighter recovery boards; every 50th level is SUPER HARD. A rectangle-silhouette rule shapes every third board (narrow tanks, notched corners, comb walls). The current campaign has 320 distinct geometries; the historical variety audit (path silhouettes 124, combined structures 325, no similar board or path run longer than two) passed.

Customization Guide

Level Format

Each Level_N.json holds the grid (cols, rows), the playable board cells and walls, the boxes (pieces: id, colour, absolute cells, footprint shape, rot, mirror, optional key / locks), the tanks (reservoirs: cells, pipe, and per column the sand queue from the bottom layer up as [colour, box cells]), the covers (cells and count), the countdown, pour speed, palette, the board family, the slot index and the proven witness. Level 18, shortened:

{
  "id": "Level_4",
  "number": 4,
  "title": "Level 4",
  "grid": {
    "cols": 4,
    "rows": 6
  },
  "playable": [
    [
      0,
      2
    ],
    [
      1,
      2
    ],
    [
      2,
      2
    ],
    [
      3,
      2
    ],
    [
      0,
      3
    ],
    [
      1,
      3
    ],
    [
      2,
      3
    ],
    [
      3,
      3
    ],
    [
      0,
      4
    ],
    [
      1,
      4
    ],
    [
      2,
      4
    ],
    [
      3,
      4
    ],
    [
      0,
      5
    ],
    [
      1,
      5
    ],
    [
      2,
      5
    ],
    [
      3,
      5
    ]
  ],
  "walls": [],
  "pieces": [
    {
      "id": "b1",
      "colour": "yellow",
      "cells": [
        [
          3,
          4
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b2",
      "colour": "darkgreen",
      "cells": [
        [
          2,
          2
        ],
        [
          3,
          2
        ]
      ],
      "shape": "box-1x2",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b3",
      "colour": "yellow",
      "cells": [
        [
          2,
          4
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b4",
      "colour": "darkgreen",
      "cells": [
        [
          1,
          2
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b5",
      "colour": "darkgreen",
      "cells": [
        [
          1,
          3
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    }
  ],
  "reservoirs": [
    {
      "id": "T1",
      "cells": [
        [
          0,
          0
        ],
        [
          1,
          0
        ],
        [
          0,
          1
        ],
        [
          1,
          1
        ]
      ],
      "pipe": "none",
      "columns": [
        {
          "col": 0,
          "queue": [
            [
              "darkgreen",
              2
            ]
          ]
        },
        {
          "col": 1,
          "queue": [
            [
              "yellow",
              2
            ]
          ]
        }
      ]
    },
    {
      "id": "T2",
      "cells": [
        [
          2,
          0
        ],
        [
          3,
          0
        ],
        [
          2,
          1
        ],
        [
          3,
          1
        ]
      ],
      "pipe": "none",
      "columns": [
        {
          "col": 2,
          "queue": [
            [
              "darkgreen",
              1
            ]
          ]
        },
        {
          "col": 3,
          "queue": [
            [
              "darkgreen",
              1
            ]
          ]
        }
      ]
    }
  ],
  "covers": [],
  "timerSeconds": 180,
  "coins": 0,
  "reward": 40,
  "doubleReward": 0,
  "featurePct": 0,
  "featureTitle": "New Feature!",
  "featureIcon": "silhouette",
  "introSeconds": 0,
  "timerStartDelay": 0,
  "fillPctPerSec": 100,
  "boostersLockedBar": false,
  "palette": {
    "white": "#EEF0FA",
    "black": "#34323A",
    "purple": "#8A3CF0",
    "darkgreen": "#15806A",
    "orange": "#F2821E",
    "yellow": "#FAD21E",
    "blue": "#2E6CF0",
    "green": "#2ED85A",
    "red": "#EC3C48",
    "pink": "#E64EE6"
  },
  "index": 4,
  "family": "twin",
  "witness": [
    {
      "piece": "b5",
      "relocation": false,
      "path": [
        [
          0,
          3
        ],
        [
          0,
          2
        ]
      ]
    },
    {
      "piece": "b4",
      "relocation": false,
      "path": [
        [
          0,
          2
        ]
      ]
    },
    {
      "piece": "b3",
      "relocation": false,
      "path": [
        [
          1,
          4
        ],
        [
          1,
          3
        ],
        [
          1,
          2
        ]
      ]
    },
    {
      "piece": "b1",
      "relocation": false,
      "path": [
        [
          2,
          4
        ],
        [
          1,
          4
        ],
        [
          1,
          3
        ],
        [
          1,
          2
        ]
      ]
    }
  ]
}

Support

Questions and bug reports: satisvizion@gmail.com. Please include the Unity version, the level number and the editor console output.

Current delivery and examples

Unity 6000.0.62f1 / URP. Support: satisvizion@gmail.com.

Buyer roots: Assets/Game, Assets/HyperCasualLiveOpsKit. Internal production, build and upload folders are excluded. The kit is an owner-approved first-party asset.

Current campaign: 500 levels. Use the shipped Level Editor and Auto Level Generator menu entries: Tools/Sand Blocks/Documentation, Tools/Sand Blocks/Level Editor, Tools/Sand Blocks/Auto Level Generator.

Current complete level example

Assets/Game/Resources/Levels/Level_4.json

{
  "id": "Level_4",
  "number": 4,
  "title": "Level 4",
  "grid": {
    "cols": 4,
    "rows": 6
  },
  "playable": [
    [
      0,
      2
    ],
    [
      1,
      2
    ],
    [
      2,
      2
    ],
    [
      3,
      2
    ],
    [
      0,
      3
    ],
    [
      1,
      3
    ],
    [
      2,
      3
    ],
    [
      3,
      3
    ],
    [
      0,
      4
    ],
    [
      1,
      4
    ],
    [
      2,
      4
    ],
    [
      3,
      4
    ],
    [
      0,
      5
    ],
    [
      1,
      5
    ],
    [
      2,
      5
    ],
    [
      3,
      5
    ]
  ],
  "walls": [],
  "pieces": [
    {
      "id": "b1",
      "colour": "yellow",
      "cells": [
        [
          3,
          4
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b2",
      "colour": "darkgreen",
      "cells": [
        [
          2,
          2
        ],
        [
          3,
          2
        ]
      ],
      "shape": "box-1x2",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b3",
      "colour": "yellow",
      "cells": [
        [
          2,
          4
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b4",
      "colour": "darkgreen",
      "cells": [
        [
          1,
          2
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    },
    {
      "id": "b5",
      "colour": "darkgreen",
      "cells": [
        [
          1,
          3
        ]
      ],
      "shape": "box-1x1",
      "rot": 0,
      "mirror": false
    }
  ],
  "reservoirs": [
    {
      "id": "T1",
      "cells": [
        [
          0,
          0
        ],
        [
          1,
          0
        ],
        [
          0,
          1
        ],
        [
          1,
          1
        ]
      ],
      "pipe": "none",
      "columns": [
        {
          "col": 0,
          "queue": [
            [
              "darkgreen",
              2
            ]
          ]
        },
        {
          "col": 1,
          "queue": [
            [
              "yellow",
              2
            ]
          ]
        }
      ]
    },
    {
      "id": "T2",
      "cells": [
        [
          2,
          0
        ],
        [
          3,
          0
        ],
        [
          2,
          1
        ],
        [
          3,
          1
        ]
      ],
      "pipe": "none",
      "columns": [
        {
          "col": 2,
          "queue": [
            [
              "darkgreen",
              1
            ]
          ]
        },
        {
          "col": 3,
          "queue": [
            [
              "darkgreen",
              1
            ]
          ]
        }
      ]
    }
  ],
  "covers": [],
  "timerSeconds": 180,
  "coins": 0,
  "reward": 40,
  "doubleReward": 0,
  "featurePct": 0,
  "featureTitle": "New Feature!",
  "featureIcon": "silhouette",
  "introSeconds": 0,
  "timerStartDelay": 0,
  "fillPctPerSec": 100,
  "boostersLockedBar": false,
  "palette": {
    "white": "#EEF0FA",
    "black": "#34323A",
    "purple": "#8A3CF0",
    "darkgreen": "#15806A",
    "orange": "#F2821E",
    "yellow": "#FAD21E",
    "blue": "#2E6CF0",
    "green": "#2ED85A",
    "red": "#EC3C48",
    "pink": "#E64EE6"
  },
  "index": 4,
  "family": "twin",
  "witness": [
    {
      "piece": "b5",
      "relocation": false,
      "path": [
        [
          0,
          3
        ],
        [
          0,
          2
        ]
      ]
    },
    {
      "piece": "b4",
      "relocation": false,
      "path": [
        [
          0,
          2
        ]
      ]
    },
    {
      "piece": "b3",
      "relocation": false,
      "path": [
        [
          1,
          4
        ],
        [
          1,
          3
        ],
        [
          1,
          2
        ]
      ]
    },
    {
      "piece": "b1",
      "relocation": false,
      "path": [
        [
          2,
          4
        ],
        [
          1,
          4
        ],
        [
          1,
          3
        ],
        [
          1,
          2
        ]
      ]
    }
  ]
}

Level fields

FieldJSON type
idstr
numberint
titlestr
griddict
playablelist
wallslist
pieceslist
reservoirslist
coverslist
timerSecondsint
coinsint
rewardint
doubleRewardint
featurePctint
featureTitlestr
featureIconstr
introSecondsint
timerStartDelayint
fillPctPerSecint
boostersLockedBarbool
palettedict
indexint
familystr
witnesslist

Safe worked editing procedure

  1. Open Level 4 in the Level Editor and duplicate it into an unused slot.
  2. Use the editor’s supported controls for a small placement change; keep legal occupancy and required targets. Do not invent JSON fields.
  3. Run the shipped validator, correct any rule errors, save, then open the duplicate in gameplay and complete it.
  4. For generation, record the current seed, request a small append batch using the exposed controls, validate its solutions and confirm the previous campaign prefix is unchanged. Cancelling must leave the campaign unchanged.

Run these procedures in a clean import of the final candidate before claiming buyer acceptance; restore test additions afterward.

Current booster policy

{
  "schemaVersion": 1,
  "gameId": "sand-blocks",
  "defaultProfileId": "default",
  "initialGrant": 2,
  "refillAmount": 1,
  "baseCoinCost": 100,
  "coinCostStep": 25,
  "levelsPerPriceStep": 10,
  "rewardedTimeoutSeconds": 90,
  "boosters": [
    {
      "id": "sandblocks_freeze",
      "unlockLevel": 2,
      "rewardedPlacementId": "booster_refill_sandblocks_freeze",
      "reason": "Module 04 default first unlock (level 2). Instant and always useful: every level runs on the countdown; teaches the booster shelf without a targeting step. The recorded original's Level 1 shows four boosters locked until Lv.7 / Lv.9 / Lv.13 / Lv.17 (freeze, hammer, magnet, UFO; the developer did not implement the magnet); the production policy default is used instead."
    },
    {
      "id": "sandblocks_hammer",
      "unlockLevel": 7,
      "rewardedPlacementId": "booster_refill_sandblocks_hammer",
      "reason": "Module 04 default second unlock (level 7). First targeted booster: every level has visible, unlocked, unfilled boxes at its start, so a valid target exists at its introduction; most useful once a box is walled in."
    },
    {
      "id": "sandblocks_ufo",
      "unlockLevel": 12,
      "rewardedPlacementId": "booster_refill_sandblocks_ufo",
      "reason": "Module 04 default third unlock (level 12). Targeted and strongest (clears a whole colour from the board and the reservoirs), so it comes last, when boards carry several boxes per colour and layered reservoirs."
    }
  ]
}

Hosting, native-device tests and live-service verification are separately recorded in the delivery handoff. A local build is not proof of a hosted release.

Fonts

Unity-provided defaults only: TextMesh Pro default font for TMP text and LegacyRuntime.ttf for legacy Unity UI. Official TMP Essentials must be installed. Do not add custom font files or atlases. Any historical custom-font references above describe an older revision; they are not current dependencies.

Demo and publishing configuration

Current source verified on 2026-10-09. The Android download is a development demo with debug signing; WebGL is a LocalQA showcase. Ads are included as LiveOps provider adapters and test/local configuration; vendor SDKs and production credentials are not bundled in the buyer package. Native device and live store certification remain pending.

For product help, contact SatisVizion at satisvizion@gmail.com

See Third-Party Notices.txt in the package for the verified dependency and license inventory.

HyperCasual LiveOps Kit manual

Managed campaign save guard

The editor save guard ignores JSON formatting whitespace outside string literals, including CRLF/LF and pretty-print layout. Keys, values, array order and characters inside strings remain protected: reconcile actual external edits before saving. This editor-only correction preserves all current runtime level bytes and gameplay rules.

Current route validation

All 500 current runtime and authoring winning certificates passed. A separate conservative permanent-wall reachability guard reports 94 existing warnings; these do not prove those levels impossible or establish exhaustive safety for every possible player order. New generator candidates must pass that guard plus exact winning and timer replay. Human difficulty balance remains unmeasured.

IL2CPP procedural primitives

The packaged Game/link.xml preserves the mesh and collider components required by procedural booster primitives. This fixes an engine-stripping error observed in the first WebGL demo. Android and WebGL were rebuilt with the preservation file; gameplay rules and the 500 current levels remain unchanged.