# Code style Code should have clear indentation and generous spacing between logical steps, so a reader can follow it without unpacking a dense block. ## Tabs and layout - Use real tabs for Luau/Lua indentation, displayed at four columns. - Use spaces around operators and after commas. Let StyLua handle continuation indentation; do not align columns by hand. - Keep lines within 120 columns where practical. Wrap long signatures and calls with one argument per line. - Prefer double-quoted strings, LF line endings, a final newline, and no trailing whitespace. - Keep one statement per line. Do not compress guards, loops, or functions onto a single line. `stylua.toml` defines formatter settings. `.editorconfig` gives editors the same tab and newline conventions. ## Vertical spacing Use one blank line to separate meaningful groups: - Module comments, services, dependencies, types/constants, and the module implementation. - Local declarations from the work that uses them. - Guard clauses from the main path. - Loops and conditionals from the next independent step. - A side effect or calculation from the final return. - Function definitions, including methods inside a returned API table. Keep related assignments together, such as updating a position's X/Y values or configuring a UI object. Do not put blank lines between every table field or every assignment. Avoid several consecutive blank lines, decorative dividers, and empty lines immediately inside a block. ```luau local function join(player: Player, token: number): () local entry = players[player] if not entry or entry.token ~= token then return end local room = rooms[entry.roomId] if not room then publish(player, "This lobby is no longer available.") return end room.guest = player entry.ready = true startMatch(room) end ``` StyLua preserves intentional blank lines; grouping statements remains the author's responsibility. ### Block endings After the final `end` in a guard, loop, conditional branch, callback, or function, leave one blank line before the next independent statement. This makes the end of one piece of logic visible before the next piece begins. Keep `else`, `elseif`, `until`, closing parentheses, commas, and table braces adjacent to the `end` they continue. ```luau if not player then return end local profile = profiles[player] for _, item in ipairs(items) do process(item) end publish(profile) ``` Do not add blank lines between an `end` and the `end` of its containing block. Do not use several blank lines or put an empty line immediately after an opening block. One blank line is the standard separator. ### Local declarations Keep consecutive local declarations together. After the final local variable declaration, leave one blank line before the statement that uses those values. This separates setup from work while keeping related variables easy to scan. ```luau local bounceProgress = 1 - bounceTime / BOUNCE_DURATION local compression = math.exp(-progress * 4) * math.cos(progress * 8) local normal = 1 - compression * 0.42 * bounceStrength local tangent = 1 + compression * 0.5 * bounceStrength setBallSize( BALL_WIDTH * (if bounceHorizontal then normal else tangent), BALL_HEIGHT * (if bounceHorizontal then tangent else normal) ) ``` Do not put blank lines between variables that belong to the same setup group. Keep multiline declarations together until their closing expression. ### Assignment groups Treat assignments that update state the same way as local declarations. Consecutive assignments to related variables may stay together, but separate that group from calls, conditionals, loops, and other work with one blank line on each side. This keeps a state change visually distinct from the operation that follows it. ```luau setBallSize(width, height) bounceTime = math.max(0, bounceTime - dt) setBallSize(BALL_WIDTH, BALL_HEIGHT) ``` Keep assignments that belong to one transition together: ```luau bounceTime = BOUNCE_DURATION bounceHorizontal = false bounceStrength = 1 touchingWall = atWall ``` ### String composition and property edits When a string is built across several lines with `..`, keep the complete assignment together, then leave one blank line before the next condition, call, or assignment. Treat property edits the same way: an assignment such as `label.TextColor3 = color` gets its own space before the next operation. A related run of property assignments may stay together when they form one transition. ```luau rewards.Text = "" .. xp .. " XP" if result.xpRequired then xpLabel.TextColor3 = gold animateXP(result) end ``` ## Modules and names Start runtime Luau modules with `--!strict`. Keep services and shared dependencies near the top, followed by types, constants, module state, helpers, and public functions. End with the module return. Use PascalCase for module names and exported types, camelCase for functions, variables, and record fields, and UPPER_SNAKE_CASE for local constants. Configuration files keep their established PascalCase keys. Prefer descriptive names; short coordinate names such as `x`, `y`, and `dt` are appropriate in simulation/rendering code. Use one name for one concept throughout a module. Choose names that describe the value's role (`player`, `profile`, `room`, `entry`, `settings`) and keep related names parallel (`host`/`guest`, `up`/`down`, `left`/`right`). Avoid switching between abbreviations and full names for the same value. Use `value` only for a genuinely generic value and prefer a domain name when its meaning is known. Keep each module responsible for one concern. Managers own state and rules; handlers connect those rules to Roblox services or presentation; libraries contain reusable behavior. Put first-party libraries in `Modules/Libraries/Internal` and third-party libraries in `Modules/Libraries/External`, under ReplicatedStorage for shared code or ServerStorage for private code. Preserve third-party licenses and formatting; `.styluaignore` excludes external libraries. Use shared types from `src/ReplicatedStorage/Modules/Types` instead of copying contracts. Use consistent public APIs for related behavior, with explicit parameters and shared implementations. ## Functions, types, and tables Prefer guard clauses over deeply nested branches. Keep simple value choices as short `if` expressions; use ordinary `if/elseif/else` statements when a choice becomes hard to read. Annotate public function parameters and return values where useful, and use shared domain types. Infer obvious local types. Avoid `any`; validate unknown remote or saved data at its boundary. Small records may stay inline if they are easy to scan. Larger records and nested configuration should use one field per line with trailing commas. Group related fields with a blank line only when it clarifies the structure. Use multiple assignment for values that belong to the same operation, such as coordinates or a coordinated state transition. Declare unrelated values separately. ## Comments and rendering Comments explain intent, constraints, or surprising behavior. Avoid narrating obvious assignments or repeating a function's name. Keep work in frame callbacks bounded and avoid unnecessary allocations. Disconnect event connections and release owned instances when their owner is destroyed. Verify rendering and input in Studio. Formatting changes must not alter runtime behavior. ## Before finishing a change Format first-party source and checks: ```sh nix-shell --run 'stylua src tests tools' ``` For behavior changes, run the existing checks: ```sh nix-shell --run 'bash tools/check.sh' ``` Use focused regression tests for bugs and real Studio checks for rendering changes. Do not reformat vendored packages or backup archives. Keep formatting-only changes separate from behavior changes when reviewing the diff.