-
Notifications
You must be signed in to change notification settings - Fork 0
Contributing
MichaelFisher1997 edited this page Jan 5, 2026
·
1 revision
Guidelines for contributing to ZigCraft, including code style, development workflow, and common implementation tasks.
The project uses Nix for dependency management. All commands must be wrapped:
nix develop --command <command>| Command | Description |
|---|---|
nix develop --command zig build |
Build project |
nix develop --command zig build run |
Build and run |
nix develop --command zig build test |
Run unit tests |
nix develop --command zig build -Doptimize=ReleaseFast |
Release build |
nix develop --command zig fmt src/ |
Format code |
nix develop --command zig build check |
Fast type-check |
rm -rf zig-out/ .zig-cache/| Element | Style | Example |
|---|---|---|
| Types/Structs/Enums | PascalCase |
RenderSystem, BlockType
|
| Functions/Variables | snake_case |
init_renderer, chunk_x
|
| Constants/Globals | SCREAMING_SNAKE_CASE |
MAX_CHUNKS, CHUNK_SIZE
|
| Files | snake_case.zig |
chunk_mesh.zig |
// 1. Standard library
const std = @import("std");
const Allocator = std.mem.Allocator;
// 2. C imports (always via c.zig)
const c = @import("../c.zig").c;
// 3. Local modules (relative paths)
const Vec3 = @import("../math/vec3.zig").Vec3;
const log = @import("../engine/core/log.zig");// Functions accepting heap allocation MUST take allocator
pub fn init(allocator: Allocator, ...) !*Self {
const self = try allocator.create(Self);
errdefer allocator.destroy(self);
self.* = .{
.data = try allocator.alloc(u8, size),
// ...
};
errdefer allocator.free(self.data);
return self;
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.data);
self.allocator.destroy(self);
}// Propagate with try
const result = try riskyOperation();
// Define subsystem error sets
pub const RhiError = error{
ShaderCompilationFailed,
OutOfMemory,
DeviceLost,
};
// Log errors
log.log.err("Failed to create buffer: {}", .{err});//! Module-level documentation (top of file)
//! Describes the module's purpose and usage.
/// Public API documentation
/// Describes function behavior, parameters, and return value.
pub fn publicFunction(param: u32) !Result {
// ...
}pub const BufferHandle = u32;
pub const TextureHandle = u32;
pub const ShaderHandle = u32;pub const PackedLight = packed struct {
block_light: u4 = 0,
sky_light: u4 = 0,
};pub const Vertex = extern struct {
pos: [3]f32,
color: [3]f32,
normal: [3]f32,
uv: [2]f32,
tile_id: u32,
skylight: u8,
blocklight: u8,
};| Coordinate | Type | Description |
|---|---|---|
| World | i32 |
Global block position |
| Chunk | i32 |
@divFloor(world, 16) |
| Local | u32 |
0-15 (X/Z), 0-255 (Y) |
pub fn worldToChunk(world_coord: i32) i32 {
return @divFloor(world_coord, 16);
}
pub fn worldToLocal(world_coord: i32) u32 {
return @intCast(@mod(world_coord, 16));
}-
Add enum entry in
src/world/block.zig:pub const BlockType = enum(u8) { // ...existing blocks... my_new_block = 40, };
-
Register properties:
pub fn isSolid(self: BlockType) bool { return switch (self) { .my_new_block => true, // ... }; } pub fn isTransparent(self: BlockType) bool { return switch (self) { .my_new_block => false, // ... }; } pub fn getColor(self: BlockType) [3]f32 { return switch (self) { .my_new_block => .{ 0.8, 0.5, 0.2 }, // ... }; } pub fn getLightEmission(self: BlockType) u4 { return switch (self) { .my_new_block => 0, // or 1-15 if emissive // ... }; }
-
Add texture in
src/engine/graphics/texture_atlas.zig:pub const TILE_MY_BLOCK = 40; // In getTilesForBlock(): .my_new_block => .{ .top = TILE_MY_BLOCK, .bottom = TILE_MY_BLOCK, .side = TILE_MY_BLOCK },
-
Update mesh logic if special rendering needed in
src/world/chunk_mesh.zig
- GLSL sources in
assets/shaders/vulkan/ - SPIR-V validated during
zig build test - Uniform names must match exactly between shader and RHI
// In src/tests.zig
test "Vec3 normalize" {
const v = Vec3.init(3, 4, 0);
const n = v.normalize();
try std.testing.expectApproxEqAbs(n.x, 0.6, 0.001);
try std.testing.expectApproxEqAbs(n.y, 0.8, 0.001);
try std.testing.expectApproxEqAbs(n.z, 0.0, 0.001);
}- NEVER call RHI or windowing from worker threads
- Use
Mutexfor shared state synchronization - Use
pin()/unpin()when passing chunks to background jobs
// Worker thread: prepare data
chunk.pending_vertices = buildMesh();
// Main thread: upload to GPU
while (upload_queue.pop()) |chunk| {
chunk.upload(vertex_allocator);
}Before submitting changes:
-
zig build testpasses (unit tests + shader validation) -
zig build -Doptimize=ReleaseFastcompiles -
zig fmt src/applied - Visual verification for graphics changes
- Performance profiling for hot paths
- Write self-documenting code
- Use comments to explain why, not what
- Follow Zig idioms:
init/deinitpairs, explicit allocators - Add tests for new utility, math, or worldgen logic
- Chunk mesh building runs on worker threads
- Avoid allocations in hot paths
- Use packed structs for large arrays
- Profile with
ReleaseFastbefore optimizing - Maximum 32 GPU uploads per frame
See Architecture for detailed module organization.
Source: AGENTS.md | Last updated: January 2026