Date: 2026-10-27 Updated: 2026-10-28. User authorized implementation, including fractal artwork controls. Status: implementation authorized; builder choices below are explicitly identified.
Author an ordered list of editable text objects in each level, following the collectible editor’s conventions. Text has real thickness, including visible side surfaces. It is fixed in level-local coordinates and participates in ray intersections and depth occlusion with the fractal. The player can move around and through it. It is not a camera-facing billboard.
Each object has an imported font, centered multiline text, local position, uniform scale, Euler rotation, and a place-in-front-of-player action. Creation uses that same placement action. Placement matches the player’s scale and orientation and fits the text to 50% of the camera frustum. Expected content is at most 200 characters, usually three or four lines; extensive international typography is outside this first iteration.
Text reads left to right from either side. When the camera crosses its plane, the object performs a small fuzz animation while its lettering mirrors. Its authored transform does not change. The design must expose spatial text sampling for a future node editor that combines text with fractal fields; the node editor remains future work. Gradient-driven geometry distortion, field-normal tint, and escape-value volume carving are now in scope.
Start with a substantial headless testing harness and develop the feature against it. No windowed application launches are authorized by this work.
Text. Default thickness is 0.15 em, centered about the
local text plane, and scales with the object. Start with neutral bright
lettering and normal-dependent shading so side faces are visible, using
the existing surface finishing path. Field tint uses the existing material
tint controls with independent text sampling scale and blend. Additional
extrusion controls can be added later without changing the field contract.Use a cached two-dimensional signed-distance field for each laid-out block,
extruded into a bounded three-dimensional field. A sample takes a point in
text-local space and returns a signed distance estimate in those same units.
Combine the 2D distance and the depth slab using the standard extrusion
distance construction; preserve interior sign, counters such as the hole in
O, front/back faces, and side walls. A rectangular mask alone is insufficient.
Godot’s TextServer provides glyph outlines, metrics, and shaping. Prefer a CPU bake from outlines, with curve-flattening error and texture discretization accounted for explicitly. Keep font access and baking behind an adapter. The first implementation gate must prove that a real imported TTF and OTF can be laid out and baked without a renderer. Do not quietly replace this with a GPU-only font rasterization path. Official API reference: https://docs.godotengine.org/en/4.5/classes/class_textserver.html#class-textserver-method-font-get-glyph-contours
The cached field is approximate. Record texel-to-local scale, signed-distance range, padding, and an error bound. Use conservative step sizes and a hit refinement policy that cannot step over a thin stroke. Stop sampling outside the field’s supported domain; clamp-to-edge texture sampling must not create infinite strips. Bake resolution, memory budget, and maximum supported close-up magnification are measured implementation choices, not claims of infinite detail. If one block texture cannot retain fine strokes at reasonable cost, the builder may use tiled/glyph field storage behind the same sampling contract.
For the initial renderer, intersect each candidate ray with the oriented text bounds and march text only over that interval, clipped by the nearest known opaque scene hit and view/fog limits. Compare text, fractal, and collectible hits in a common ray-distance metric. A scene miss must still allow a text hit, including text outside the fractal’s bounding cube. Early outs in the current shader must be audited for this requirement.
Keep the text query in a separate shader include with an explicit contract: canonical signed-field sample; conservative bounds; ray hit distance, position, normal, object ID and material ID; and a separate view-correction transform. The bounded renderer is a consumer of that field, not its definition. A future field graph can query the canonical field and enlarge/replace bounds for warps. The current gradient warp also does not preserve a distance bound; its effect path uses bounded interval sampling and refinement, as specified below.
A mask or separate pass may be useful when many text bounds overlap the screen, or when text needs a different resolution. Start with bounds rejection and bounded text queries in the existing primary-ray shader: that reuses its ray and precise hit distances, without new full-screen targets or a second ray setup. A screen-space rectangle mask is only a candidate mask, not text depth.
Do not use the existing quantized lens-depth alpha as the occlusion oracle: it is an optical-effect encoding, not sufficiently precise geometry depth. Any future split pass must retain adequate depth precision, normals/IDs as needed, and resolve the nearest hit before depth-dependent finishing. Compare the same sparse/dense workloads before claiming the split pass is faster. Separate compositing alone cannot implement future CSG or warped field sampling.
The selected visible surface goes through existing fog/scattering and level finishing, then the existing grid/painterly/lens/phosphor paths as applicable, then UI. Text uses its own hit distance for fog and optical depth. Preserve existing transition and renderer-mode behavior rather than inventing a second independent effects pipeline.
Each text entry exposes these controls in the owning shape’s level settings, in its text list. They are saved per text object and editable independently.
The following are implementation choices, not additional user requirements. Default all effects off (distortion 0, tint blend 0, dissolve disabled), source Owning shape, resample offset/rotation zero, resample scale 1, tint scale 1. Use explicit dissolve enable plus threshold so the old appearance is exact.
Preserve the selected source’s authored frame, relative to the owning shape; apply a virtual resample transform in the owner’s frame about the source’s origin. Increasing resample scale makes the borrowed pattern larger. Evaluate samples with the inverse transform. Tint’s independent scale further scales its sample coordinates about that origin and does not affect distortion or carving. Field coordinates stay anchored to the shape when text mirrors.
Stable shape identities, not list indices or labels, back explicit references. Legacy shapes receive IDs on load; reordering/renaming preserves references. A deleted source disables its dependent effects with a visible warning and retains the unresolved ID for repair. Owning shape is a dynamic sentinel that continues to mean the new owner when duplicated. Non-active source shapes may supply a field without becoming visible geometry.
Implement distortion as bounded inverse-domain displacement along a regularized DE gradient. Express its maximum displacement in text-local em units so bounds can be expanded conservatively. The gradient is sampled in the source frame and transformed back into text coordinates. Exact regularization and sampling resolution are builder choices that must be recorded with validation; never claim a warped field remains a conservative distance estimate without a bound.
Expose a production escape-field sampler for each supported fractal family. The threshold is an orbit escape scalar, not the distance estimator or the level-transition escape sphere. Pin the scalar’s formula, iteration count, bailout and non-escaping behavior in tests and UI help. Different families need not share numerically equivalent thresholds. Unsupported sources must report that fact instead of silently substituting surface distance. Avoid dependence on moving/still quality budgets: an artwork mask must not change as quality settles. Choose finite defaults/ranges from real-family fixtures.
The composed text solid is warped text intersected with the region whose escape scalar is at or below threshold. The renderer must find interior cut boundaries as well as original glyph faces. A scalar escape residual is not a signed distance: use bounded sampling/refinement or a proven step bound, and state the supported feature resolution. Preserve canonical base text queries for future field graph consumers; expose the composed solid separately.
Appearance, resampling and source-parameter edits invalidate rendering without rebaking the font. Bound and cache derived data where necessary, report its resolution and memory limits, and never silently omit excess text objects.
Define the canonical front as local +Z, text right as +X, and up as +Y. Determine view side from camera position in text-local coordinates, not camera look direction. On the back, reflect the sampled X coordinate about the block center. This corrects both glyph shapes and their order without reversing stored text or changing authored transforms. Transform normals with the same reflection.
With thick asymmetric letters, reflection necessarily changes the visible silhouette and the intersections with nearby fractal geometry. That is part of this view correction. The canonical signed field remains camera-independent so future field combinations can choose their own correction semantics.
Proposed fuzz duration: 180 ms. From 0–90 ms, increase a stable object-local noise dissolve until the text is fully hidden. At 90 ms, change the sampling handedness. From 90–180 ms, reveal the corrected text. Use noisy coverage without displacing bounds or adding rays. At full visibility the noise path is bypassed. Use stable object-ID seeding and elapsed time, not frame count.
Use a side hysteresis half-width of 0.02 em in text-local distance to suppress side-on chatter. On first visibility/load, initialize directly to the current side with no animation. During a running transition, retain a target side; before midpoint it may be updated without resetting the timer. After midpoint, finish the committed reveal and then transition again only if the camera is beyond the opposite threshold. No unbounded restart loop. Hidden/inactive objects reset transient state when next activated. None of this state is saved.
Dissolved fragments are absent from visible-depth selection: geometry behind must appear with its own depth and finishing, without invisible text occlusion. Apply the coverage decision consistently to the object’s first solid candidate for a ray; do not make front-face fuzz accidentally expose an opaque back face instead. Keep the geometric field and hit query usable independently of this effect.
Add a text section alongside collectibles: add, remove, up/down reorder, collapsible entries, font file selector, multiline TextEdit, XYZ position, XYZ Euler angles in degrees, one positive uniform scale, and the placement button. Use the project’s existing rotation convention. Connect edits by stable object identity, not a stale captured list index. Editing text must consume movement shortcuts and Return must insert a newline.
Reject nonfinite transforms and nonpositive scale with a visible validation message. Support editing up to 200 characters; reject excess input clearly without silently truncating existing or loaded data. Four lines is a workload, not a hard line limit. Unsupported glyphs receive a visible replacement and warning. Invalid loaded entries remain recoverable and are not rendered.
Import font bytes, not absolute machine paths. Proposed portable storage: one workspace-level dictionary of content-hashed font blobs, base64-encoded in the saved JSON, referenced by object font IDs. Bundle the default font in the project. Identical bytes are stored once; duplicate filenames with distinct bytes stay distinct. A disk cache is disposable and regenerates from the saved font data. Exported saves carry the same font payload without editor-only imports. Apply explicit font payload size/resource limits before parsing.
Persist stable IDs, order, text, font ID, local transform, and versioned field settings needed for reproducibility. New saves round-trip without depending on an external file; old saves load with empty text lists. Include text/font state in level cloning and workspace load/reset paths. Preserve loaded unknown data where possible, report invalid fonts, and show a deterministic fallback.
Cache layout/field by font content hash, text, and bake settings. Transform, camera, ordering, mirror transition, and appearance changes must not rebake fonts or fields. Text and font edits invalidate exactly the affected cache entries. Coalesce live typing work; ignore stale results after another edit, deletion, or level load. Bound and release cache resources.
Reuse tests/test_case.gd for deterministic CPU, persistence, and UI tests.
Add a focused headless runner which fails on exceptions, parse errors,
timeouts, missing required capabilities, and missing expected test summaries.
It must run both Godot tests and shader execution tests. Temporary test assets
and save pointers are isolated from the user’s levels and font cache.
Reuse the approach in tests/material_shader_render_test.py: compile the
production shader include and its actual integration path in headless Chromium,
read floating-point pixels, and compare hit/miss, distance, normal, ID, and
selected composed pixels with independent expected results. A copied CPU
implementation is not proof that production shader code works. Synthetic
boxes, rings, thin strokes, and analytic planes provide independent oracles;
an actual font fixture adds end-to-end coverage without relying on font-specific
OS rendering. Pin fixtures and licensing alongside the tests.
First milestone is a tested harness with analytic fixtures, runner failure injection, and the real-font headless feasibility gate. Contract test cases for unfinished product layers remain explicitly pending, not falsely passing. Enable each required test as its layer is implemented. The feature is complete only when all required cases pass and pending count is zero. Intentionally break representative depth, mirror, placement, and runner behavior in isolated fixtures to demonstrate that the tests detect those failures.
Godot headless tests do not prove its final GL driver rendering. Chromium execution checks shader math and pixels, but does not certify Godot viewport composition or macOS driver equivalence. Test the host wiring headlessly and report that remaining visual verification gap. No GPU performance claims from software-rendered Chromium timing.
ABO; the O counter remains outside the solid, without a window.F R 12 fixtures have correct handedness
and order from front/back while maintaining the same authored frame.Work budgets: empty scenes perform no text samples; missed bounds and wholly occluded bounds perform no field samples; bounded march counts, allocations, bake counts, and texture memory are reported for 1/16/64 objects. Real performance timing is reported separately from these checks.
The implementation costs and supported limits are recorded below; this section remains the checklist for later renderer changes.
After this design is approved, write the implementation plan with the harness as its first milestone, then implement in the user’s existing checkout. Existing edits are present in main, collectible UI, shader tests, and other files; preserve them and do not include unrelated staged work in commits. If concurrent changes actually collide with implementation, follow the user’s isolation rule. Commit only logical task-owned changes. Do not push, publish, merge, rebase, cherry-pick, revert, or force-delete any directory.
min(log2(1 + terminal orbit radius), 40) after a
fixed 16 iterations. Above-threshold points are removed; equality remains.
Non-escaping points use their terminal radius. Kleinian uses terminal orbit
magnitude because it has no natural escape event. Saturation prevents
overflow. Mandelbulb geometry distortion has a separate stable-bailout DE;
it never treats the escape saturation sentinel as a surface distance.python3 tests/run_world_text.py is the focused acceptance entry point. It
rejects nonzero exits, script/shader errors, missing/zero-check summaries,
missing executables and timeouts. It runs data/UI, font, placement, runtime,
1/16/64-object budgets, integration, real-font fixture generation, production
text shader execution and the existing material shader regression.
The production shader tests include analytic solid/counter fixtures, clipped
rays, source transforms, mirror/fuzz, distortion, volume carving, tint sampling,
real fractal-family samples, and the full fragment with a real baked font.
Headless preview: build/tests/world-text/preview.png (generated, not a save).
The repository-wide headless run also exposed eight test-file failures outside the focused feature: build_type, default_level, fixed_frame_rate, fractal_params, mcp_bridge, new_file, trailer_mode and ui_journey. These concern missing/default save fixtures, resolution expectations, catalogue ordering and UI lifecycle. The new_file residual is a pre-existing fast-resolution float32 roundtrip (0.1 vs 0.10000000149); its new stable-ID expectation was updated separately. New text uniform expectations in shader/noise_view tests were updated and both pass. No claim is made that the entire current checkout passes its test suite.
No windowed native Godot rendering was run. Software WebGL validates production shader math/composition and produces the preview; it does not certify native macOS/Windows GPU visuals, driver compilation latency or performance.