This skill covers advanced ZX rendering: render passes, stencil effects, custom fonts, billboard particles. Triggers on "render pass", "stencil buffer", "portal rendering", "mirror", "scope", "viewmodel", "z_index", "z-index", "depth clear", "custom font", "bitmap font", "billboard", "particles", "smoke/fire effect", "text rendering". **Load references when:** - Advanced stencil patterns → `references/render-passes.md` - Particle system examples → Reference skill body (core patterns below) - Font atlas creation → See details below - FFI details → Read `nethercore/include/zx.rs` lines 417-502 (render passes), 751-784 (font), 644-662 (billboard)
/plugin marketplace add nethercore-systems/nethercore-ai-plugins/plugin install zx-dev@nethercore-ai-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/render-passes.mdAdvanced rendering: render passes, stencil buffer, custom fonts, billboard particles.
Create portals, scopes, mirrors, FPS viewmodels, and masked UI elements using render passes.
| Function | Purpose |
|---|---|
begin_pass(clear_depth) | New pass with optional depth clear |
begin_pass_stencil_write(ref, clear_depth) | Create stencil mask |
begin_pass_stencil_test(ref, clear_depth) | Render inside mask |
begin_pass_full(...) | Full control (8 params) |
z_index(n) | 2D ordering within pass (0-255) |
Depth/Stencil Compare (compare::*):
| Constant | Value | Use |
|---|---|---|
NEVER | 1 | Never pass |
LESS | 2 | Standard depth (default) |
EQUAL | 3 | Match exactly |
LESS_EQUAL | 4 | Less or equal |
GREATER | 5 | Greater than |
NOT_EQUAL | 6 | Inverted masks |
GREATER_EQUAL | 7 | Greater or equal |
ALWAYS | 8 | Always pass |
Stencil Operations (stencil_op::*):
| Constant | Value | Effect |
|---|---|---|
KEEP | 0 | Keep current |
ZERO | 1 | Set to zero |
REPLACE | 2 | Set to ref |
INCREMENT_CLAMP | 3 | Increment, clamp |
DECREMENT_CLAMP | 4 | Decrement, clamp |
INVERT | 5 | Bitwise invert |
INCREMENT_WRAP | 6 | Increment, wrap |
DECREMENT_WRAP | 7 | Decrement, wrap |
// 1. Create mask (geometry writes to stencil, not color)
begin_pass_stencil_write(1, 0);
draw_circle(SCREEN_CX, SCREEN_CY, 200.0); // Mask shape
// 2. Render inside mask (only where stencil == ref)
begin_pass_stencil_test(1, 0);
draw_scene(); // Only visible inside circle
// 3. Return to normal
begin_pass(0);
fn render_scoped_view() {
// Create circular mask
begin_pass_stencil_write(1, 0);
draw_circle(SCREEN_CX, SCREEN_CY, 200.0);
// World only visible inside circle
begin_pass_stencil_test(1, 0);
camera_set(cam_x, cam_y, cam_z, tx, ty, tz);
draw_env();
draw_mesh(WORLD_MESH);
// Return to normal, draw scope overlay
begin_pass(0);
texture_bind(SCOPE_OVERLAY);
draw_sprite(0.0, 0.0, 960.0, 540.0);
}
fn render_portal() {
// Draw main world first
camera_set(main_cam...);
draw_mesh(MAIN_WORLD);
// Create portal mask
begin_pass_stencil_write(1, 0);
push_translate(portal_x, portal_y, portal_z);
draw_mesh(PORTAL_FRAME);
push_identity();
// Other world inside portal (clear_depth=1 for separate view)
begin_pass_stencil_test(1, 1);
camera_set(other_cam...);
draw_mesh(OTHER_WORLD);
// Return to normal
begin_pass(0);
}
fn render_fps() {
// Draw world
camera_set(player_x, player_y, player_z, look_x, look_y, look_z);
draw_env();
draw_mesh(LEVEL);
// New pass with depth clear - viewmodel always renders on top
begin_pass(1); // clear_depth = 1
push_translate(0.3, -0.2, 0.5);
push_rotate_y(sway_angle);
draw_mesh(GUN);
push_identity();
}
fn render_vignette() {
draw_scene();
begin_pass_stencil_write(1, 0);
draw_circle(SCREEN_CX, SCREEN_CY, 250.0);
// Render OUTSIDE mask using NOT_EQUAL
begin_pass_full(
compare::LESS, 1, 0,
compare::NOT_EQUAL, 1,
stencil_op::KEEP, stencil_op::KEEP, stencil_op::KEEP,
);
set_color(0x000000AA);
draw_rect(0.0, 0.0, 960.0, 540.0);
begin_pass(0);
}
See references/render-passes.md for more advanced patterns (diagonal split, animated portals).
Load bitmap fonts for styled text rendering.
| Function | Purpose |
|---|---|
load_font(tex, w, h, first, count) | Fixed-width font |
load_font_ex(tex, widths, h, first, count) | Variable-width |
font_bind(handle) | Bind for draw_text |
Glyphs in row-major grid. For ASCII printables: first_codepoint=32, count=96.
static mut GAME_FONT: u32 = 0;
fn init() {
let tex = rom_texture_str("pixel_font");
GAME_FONT = load_font(tex, 8, 12, 32, 96); // 8x12 glyphs
}
fn render() {
font_bind(GAME_FONT);
draw_text_str("SCORE: 1000", 10.0, 10.0, 24.0, 0xFFFFFFFF);
font_bind(0); // Back to default
}
| Style | Glyph | Atlas |
|---|---|---|
| Tiny | 4x6 | 64x36 |
| Small | 8x8 | 128x48 |
| Medium | 8x12 | 128x72 |
| Large | 16x16 | 256x96 |
Camera-facing sprites for effects.
| Function | Purpose |
|---|---|
draw_billboard(w, h, mode, color) | Full texture |
draw_billboard_region(w,h, sx,sy,sw,sh, mode, color) | UV region |
| Mode | Constant | Use |
|---|---|---|
| 1 | SPHERICAL | Particles (faces camera) |
| 2 | CYLINDRICAL_Y | Trees, grass (upright) |
const MAX: usize = 256;
#[derive(Copy, Clone, Default)]
struct Particle { x: f32, y: f32, z: f32, vx: f32, vy: f32, vz: f32, life: f32, size: f32, color: u32 }
static mut PARTICLES: [Particle; MAX] = [Particle { x:0.0, y:0.0, z:0.0, vx:0.0, vy:0.0, vz:0.0, life:0.0, size:1.0, color:0xFFFFFFFF }; MAX];
fn update_particles() {
let dt = delta_time();
for p in unsafe { PARTICLES.iter_mut() } {
if p.life > 0.0 {
p.x += p.vx * dt; p.y += p.vy * dt; p.z += p.vz * dt;
p.vy -= 9.8 * dt;
p.life -= dt;
}
}
}
fn render_particles(tex: u32) {
texture_bind(tex);
for p in unsafe { PARTICLES.iter() } {
if p.life > 0.0 {
let alpha = ((p.life / 1.0) * 255.0) as u8;
push_translate(p.x, p.y, p.z);
draw_billboard(p.size, p.size, 1, (p.color & 0xFFFFFF00) | alpha as u32);
push_identity();
}
}
}
Smoke: vx/vz = ±0.5, vy = 1-2, life = 1-3s, color = 0x888888FF
Fire: vy = 2-3, life = 0.3-0.7s, colors = 0xFFFF88/FF8800/FF4400
Sparks: vx/vy/vz = ±5, life = 0.2-0.5s, size = 0.05-0.15, color = 0xFFFF00FF
| Particles | Impact |
|---|---|
| 0-100 | Excellent |
| 100-500 | Good |
| 500-1000 | Moderate |
| 1000+ | Optimize |
Tips: Pool particles, LOD at distance, texture atlas, cull behind camera.
All rendering is display-only. Particle state in static variables (auto-snapshotted). Use FFI random() for spawn variation.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.