# Dirty tracking Tecs tracks value dirtiness per archetype and component. One mark means that some row in the column may have changed. Structural residue separately records bounded append and swap-pop ranges for incremental consumers. Incremental consumers such as text layout and hierarchy composition use that signal to skip unchanged columns. A write that Tecs cannot see leaves those consumers with stale data. ## Declare write intent Read through `get` and write through `getMut`: ```nupp for archetype, length in movers:iter() do local transforms = assert(archetype:getMut(tecs.ecs.Transform2D)) local velocities = assert(archetype:get(Velocity)) unsafe do for row = 1, length as integer do local transform = transforms[row] local velocity = velocities[row] transform.x = transform.x + velocity.x * dt transform.y = transform.y + velocity.y * dt end end end ``` `getMut` marks the entire component column dirty before returning it. Never call it speculatively in a loop that might not write. For a conditional write, read first and mark only when the condition succeeds: ```nupp local transforms = assert(archetype:get(tecs.ecs.Transform2D)) local changed = false unsafe do for row = 1, length as integer do if needsCorrection(transforms[row]) then correct(transforms[row]) changed = true end end end if changed then archetype:markComponentDirty(tecs.ecs.Transform2D) end ``` A value read does not declare write intent. A field assignment through `world:get` or `archetype:get` changes memory without marking it. Prefer `getMut`; otherwise call `world:markComponentDirty` or the archetype marker after the write. ## Automatic marks These paths maintain dirty state: - `getMut` and `world:set` - spawn placement - movement into another archetype - swap-pop after removal Spawn and structural movement mark every component on the archetype, because a row moving in has every column newly written at that row. Each changed column is marked on its archetype. `world:update` clears marks after the pipeline runs. A consumer must inspect the columns it reads before those marks clear. `world:dirtyArchetypes()` visits the first-dirtied queue, not the world's whole archetype registry. Each archetype enters that queue once per dirty window. Use `structuralCount()` and the structural residue methods to decide whether a retained consumer can update ranges or must rebuild. `trackValueCount(Component)` opts a column's explicit writes into a monotonic aggregate counter; structural changes have their own counter. ## Retained rendering The renderer also keeps monotonic write counters. Those counters survive the end-of-frame dirty clear. Unchanged renderable archetype runs keep their packed instances and GPU buffers; a changed run rewrites and uploads its affected range. Structural changes that move packed runs rebuild the layout. `getMut`, `set` and explicit dirty marks therefore reach the next render even when extraction skipped a frame. Camera movement updates the view and culling while world-space instance data stays resident. Screen-space geometry and fixed-step interpolation also track the view and interpolation fraction that affect their presented positions.