OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux commits / 80a1ee4

Write Blender's splat contract by default — the poisoned name was folklore
`splat_contract` was opt-in because `color` is the attribute name this project had been burned by: "poisoned for points, renders black". That warning was INHERITED from the volume work and never measured in this context, and it is wrong as applied here. A/B on a cloud known to render, same material reading `ifs_color`, identical camera, with and without the `color` attribute: without color: lit=69 max=0.8157 mean=0.00103 with color: lit=69 max=0.8157 mean=0.00103 byte-identical `color` renders black when a MATERIAL READS it. Merely carrying the attribute changes nothing. Those are different claims and the codebase had collapsed them into one, which is how a real finding turns into a rule nobody re-checks. So both contracts are written now: the cloud renders through the points path today via `ifs_color`, and is readable by Blender's splat path when it ships. The invariant that actually matters — our material reads `ifs_color`, never `color` — is unchanged and still pinned by test_points_operator. Two tests that encoded the folklore as "this attribute must not exist" are rewritten to assert the real invariant instead, with the measurement recorded so the next person does not have to redo it. The opt-out survives as `splat_contract=False` and has its own test. 12 tests here, test_points_emit 10/10, test_points_operator 7/7, test_splat_shading 7/7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
80a1ee4e0d1a4f3166dcb56bb6dabe751c9dd4cc
by plankatron <fleeky@gmail.com> · 2026-08-18T14:44:49.000Z

Changed files (3) +40 -22

blender/ifs_points.py +13 -9
……@@ -346,7 +346,7 @@ def emit_from_file(document_path, capacity=1_000_000, max_dispatches=400,
346346
347347
348348def to_pointcloud(positions, colors, radius, name="IFS Points",
349 splat_contract=False):
349 splat_contract=True):
350350 """
351351 Builds a Blender PointCloud from the emitted arrays.
352352
……@@ -436,14 +436,18 @@ def to_pointcloud(positions, colors, radius, name="IFS Points",
436436 #
437437 # There is NO `opacity` attribute and no `sh_0_0` in the implementation.
438438 #
439 # WHY THIS IS OPT-IN. `color` is the one attribute name this project has
440 # already been burned by: as a POINT attribute it renders BLACK, which is why
441 # the colour lives in `ifs_color` and why a test forbids the name outright.
442 # Blender's splat path nevertheless requires exactly that name. The two
443 # contracts collide, and nothing that ships today reads the splat one — it is
444 # 5.3-alpha, on a branch, with no milestone — so imposing it by default would
445 # risk the rendering that works now to satisfy a renderer nobody can run.
446 # Ask for it when targeting that path.
439 # ON BY DEFAULT, and the "poisoned name" worry was MEASURED rather than
440 # inherited. `color` renders black when a MATERIAL READS it; merely carrying
441 # the attribute changes nothing. A/B on a cloud known to render, same material
442 # reading `ifs_color`, with and without this block:
443 #
444 # without color: lit=69 max=0.8157 mean=0.00103
445 # with color: lit=69 max=0.8157 mean=0.00103 (byte-identical)
446 #
447 # So the two contracts do not actually collide, and writing both means the
448 # cloud is renderable by the points path today and by the splat path when it
449 # ships. The invariant that DOES matter is that our material keeps reading
450 # `ifs_color` — test_points_operator pins that.
447451 #
448452 # `opacity` is kept alongside regardless: Blender does not read it, but it is
449453 # OUR per-point data surfaced under an obvious name, and it costs one array.
blender/test_points_emit.py +4 -3
……@@ -275,9 +275,10 @@ class Emit(unittest.TestCase):
275275 self.assertLess(float(np.abs(out.reshape(take, 3) - positions).max()), 1e-6)
276276
277277 self.assertIn(ifs_points.COLOR_ATTRIBUTE, cloud.attributes)
278 self.assertNotIn(
279 "color", [a.name for a in cloud.attributes],
280 "`color` is a poisoned attribute name for points — it renders black")
278 # `color` is now written too, carrying the raw degree-0 SH coefficient for
279 # Blender's splat path. Measured: its presence does not change the points
280 # render at all. What must stay true is that the MATERIAL reads ifs_color —
281 # pinned by test_points_operator, not here.
281282 got = np.zeros(take * 4, dtype=np.float32)
282283 cloud.attributes[ifs_points.COLOR_ATTRIBUTE].data.foreach_get('color', got)
283284 self.assertLess(
blender/test_points_splat_attributes.py +23 -10
……@@ -108,8 +108,10 @@ class SplatAttributes(unittest.TestCase):
108108 self.assertEqual(N, len(cloud.points))
109109 self.assertLess(float(np.abs(_read(cloud, 'position', 'vector', 3) - positions).max()), 1e-6)
110110 self.assertIn(ifs_points.COLOR_ATTRIBUTE, cloud.attributes)
111 self.assertNotIn('color', [a.name for a in cloud.attributes],
112 '`color` is poisoned for points — it renders black')
111 # `color` IS present now (see the splat-contract tests below). The
112 # invariant that matters is that our MATERIAL keeps reading `ifs_color`,
113 # which test_points_operator pins — merely carrying `color` was measured
114 # to change nothing about the points render.
113115 self.assertLess(
114116 float(np.abs(_read(cloud, ifs_points.COLOR_ATTRIBUTE, 'color', 4)[:, :3]
115117 - colors[:, :3]).max()), 1e-6)
……@@ -211,17 +213,28 @@ class SplatAttributes(unittest.TestCase):
211213 # 0.5 is the neutral point: it encodes to exactly zero.
212214 self.assertAlmostEqual(0.0, float(ifs_points.encode_sh_dc(0.5)), places=9)
213215
214 def test_the_splat_contract_is_off_by_default(self):
216 def test_the_splat_contract_is_on_by_default(self):
215217 """
216 `color` is POISONED as a point attribute — it renders black, which is why
217 the colour lives in `ifs_color`. Blender's splat path nonetheless requires
218 that exact name, so the two contracts collide and the splat one must be
219 asked for rather than imposed on the rendering that works today.
218 `color` used to be forbidden here on the grounds that the name is poisoned
219 for points. That was inherited, not measured, and the measurement says
220 otherwise: rendering a cloud that DOES render, with the same material
221 reading `ifs_color`, is byte-identical with and without the attribute
222 (lit=69, max=0.8157, mean=0.00103 both ways). `color` renders black when a
223 MATERIAL READS it, which is a different claim. So both contracts are
224 written and the cloud is renderable by the points path today and by the
225 splat path when it ships.
220226 """
221227 cloud, _pos, _col = self._cloud()
222 self.assertNotIn('color', [a.name for a in cloud.attributes],
223 'the splat contract wrote `color` without being asked — '
224 'that name renders black for points')
228 self.assertIn('color', [a.name for a in cloud.attributes],
229 'the splat contract is meant to be on by default now')
230
231 def test_the_splat_contract_can_still_be_turned_off(self):
232 """The opt-out remains, for anything that needs a cloud with no `color`."""
233 import ifs_points
234 positions, colors = _sample_arrays()
235 cloud = ifs_points.to_pointcloud(positions, colors, radius=0.01,
236 name='no-splat', splat_contract=False)
237 self.assertNotIn('color', [a.name for a in cloud.attributes])
225238
226239 def test_the_splat_contract_writes_colour_and_opacity_together(self):
227240 """