[WIP]: Refractor layout and render system - #56
Draft
Windorain wants to merge 210 commits into
Draft
Conversation
…ZipFile scans in GuidePageLanguageIndex/GuideResourceLanguageIndex
…l methods, add scan result caching - Killed dead code: load(), discoverGuideLanguages(), scanResourcePack* chain - Inlined 12 pure-function helpers (counters, freeze/format helpers, path utilities) - Merged cold-path scan methods into their single callers - Added scan result caching: same pack roots + folder → skip re-scan - Skip pre-init reload in DefaultGuideResourcePackManager (0 guides always) - Reduced method count from 57 to ~30, file from 1455 to 754 lines
…o opt/guide-load-time
…mbolicColor deprecated
…variants - Wrap::WordOrGlyph + float-wrap band splitting (narrow beside float, full width below) - rich text spans into glyph runs (argb/shear/decoration rects), justification pass - min_h bridge for <br clear> so following blocks stack below floats - harness: 19 scenarios incl. floatClearPage + glyph-vs-float overlap invariant - fix order-dependent ThemeTokenSystemSmokeTest.testNameLookup (registry snapshot)
…lips/justify_pass deleted - parley 0.11 BreakerState per-line widths replace band pre-split (float_clips schema) - inline blocks become parley InlineBox (wrap width native, vertical growth stays post-pass) - rich spans ride StyleProperty::Brush(span index); justify = native Alignment::Justify - FFFC anchors stripped before shaping (no more .notdef placeholders) - shapeText JNI (GuideText.emitText path) switched to parley; cosmic rasterizer deleted - spike: parley_probe numeric A/B PASS, parley_render side-by-side PNG identical - harness 19 scenarios 0 issues; runParleySmoke JNI A/B green
…s-taffy NO-GO spike + arch principles); carries in-flight WIP
…= the bridge), Java float wrapper eliminated + inner carries real float (gap via margin), float-replay scaffolding deleted; harness 20 scenarios 0 issues + unit tests green; layout-geometry single authority = Rust
…ecipe output arrow-gap -> output.marginLeft, toolbar label flex-grow -> LytBlock.flexGrow field; delete applyChildRules + marginLeftAdjust/flexGrowOverrides maps. Table column-width pin retained (runtime column model, deferred to grid migration). Zero new regression: recipe/toolbar/table scenarios green. NOTE: harness floatClearPage 1 issue is a PRE-EXISTING Java legacy clear bug (reproduced at c950f74 via git stash), unrelated to this commit — tracked separately
…ly declares). Schema TextData.clears [ClearBreak{raw_offset,side}]; Java serializer records each <br clear> at its raw UTF-8 byte offset (walkClears mirrors extractFlowText); Rust collect_layout drops lines after the covering line to the cleared floats' bottom edge and grows content height. Exact for the only real form (trailing clear); mid-paragraph clear is first-order (never mispositions). harness floatClearPage now GREEN (follower y=130 >= floatBottom=125) -> 20/20; test --rerun-tasks green. NOTE: harness reads follower.getBounds() which updateLayout fills from the RUST layout, so fixing Rust alone turned it green — no Java LineBuilder/harness change needed, confirming Rust is the layout authority. generated.rs + src/test are gitignored (not committed). LayoutStyleExtractor/LayoutTreeSerializer carry jump-5 spotless format debt, reverted out of this atomic commit
…t not hugging' bug). Root cause: collect_layout added the clear floor into the paragraph's own content_height, so a paragraph ending with <br clear=all> (the only real form, e.g. large_sifter.md intro) stretched its box to the float bottom — text sat at the top, leaving the blank gap the screenshot shows, and the following callout landed at the float bottom looking detached. CSS-correct fix: a trailing clear must NOT stretch the current paragraph; it pushes the flow AFTER it down. collect_layout now returns the clear floor instead of baking it into content_height; the pusher advances its cursor to max(natural, floor) after the paragraph, so the callout starts below the float while the intro hugs its text. Mid-paragraph clears are unaffected (their dropped later lines already raise content_height, so the natural cursor advance already clears the floor). Verified: harness floatClearPage intro now height=10 (was 125), follower y=130 >= floatBottom=125; new assertion 'intro inflated by trailing clear' added + floatAwareBlockPage guard scenario added (both in the gitignored local harness); 20/20 scenarios, test green. NOTE: the regression guard lives in src/test (gitignored by project convention), so it is local-only
…-stack). Schema TextData.breaks [uint] = raw byte offsets of non-clearing <br> in the break-free text; the Rust measure splits the paragraph at these offsets and shapes each piece independently, stacking them vertically (piece k+1 starts at the accumulated height), so <br> produces a real line break under parley 0.11 which has no white-space control and would fold a literal newline into a space. Clearing breaks are deliberately excluded from breaks (handled by the clear-floor path; a trailing clear on a segment boundary would otherwise be lost). PRE_WRAP code bodies stay on the legacy path: parley 0.11 exposes no Preserve collapse, so migrating them would drop indentation — tracked as a parley-capability blocker, not done here. Two offset-accumulation bugs fixed during observe: walkClears/walkBreaks were given a fresh offset counter per flow node (so every break recorded offset 0), and the harness DIAG caught breaks=[0] collapsing the split into a single shape. Verified: new breakWrapPage scenario height=20 (was 10), floatClearPage follower y=130 still green, 20/20 scenarios, test green. Regression guard + DIAG scaffolding live in the gitignored local harness
…deferred format debt + this session's edits). No logic change — verified whitespace/line-wrap only (e.g. NodeAdjustments record param rewrap); compileJava + test + runLayoutDump 20/20 green
Delete FlowBuilder/LineBuilder/Line/LineElement/LineBlock/LineTextRun (1,179 lines). All paragraph layout is now Rust's single authority; Java only declares intent. Changes: - Delete LytBalancedColumns, LytPlaceholderBlock, mode2 rendering - Delete javaHeight/Math.max fallback, fontHandle==0 defense - Delete deprecated horizontalLayout overloads, LayoutContext dead methods - Delete LayoutStyleExtractor dead flags, LayoutTreeSerializer dead fields - PRE_WRAP/Spoiler non-opaque via Rust glyph-run path - §k non-opaque: Rust shapes layout, GuideText renders per-frame animation - LytParagraph: FlowBuilder→ArrayList; computeLayout skips all LineBuilder; clean render() no-op, pickContent/getFirstLineBounds fallback paths - Delete onExternalLayoutApplied/onLayoutMoved dead content.move() calls - Schema InlineBlockRef.align docs add 3=FLOAT_LEFT 4=FLOAT_RIGHT - Cargo.toml add lib crate-type for space_probe example Harness 20/20 0 issues.
Two-pass shaping for inline blocks with align=3 (float-left) or align=4 (float-right). Pass 1 shapes all inlines at full width to determine each float's anchor line via layout.lines().text_range(); pass 2 skips float inline boxes and shapes with a merged document + paragraph-local float table constraining subsequent line widths. Changes: - InlineSpec: add height, float_side, node fields - GlyphAccum/ParleyShaped: add float_anchors (node_index, para_rel_y) - shape_paragraph: two-pass path when any InlineSpec.float_side.is_some(); extracted push_defaults/push_spans/push_inlines/break_and_align/ break_with_floats helpers - measure_text: fill float_side + height from InlineBlockRef.align - inline_post_pass: regular markers→inline anchoring (unchanged); float markers (align≥3)→position at paragraph left/right edges using computed taffy sizes Harness 20/20 0 issues.
…wText fallback, A-class SIZE_FROM_JAVA_BOUNDS removal, delete dead GuidePrimitiveCollector T0: LytHeading overrides computePrimitives (FillRect separator, geometry/colors match legacy render) T1: LytParagraph usePrimitives=!flowContent.isEmpty + emitTextFallback via GuideText.emitText; GuideScreen pageTitle routed through PrimitiveCollector+Engine T4: A-class blocks declare sizes (GuiSprite excluded from flag, inline FloatAbs reads declared sizes, buildLatexData uses formulaDisplayW/H) T7a: GuidePrimitiveCollector.java deleted (zero references) Gate: compileJava compileTestJava test runLayoutDump green, TOTAL ISSUES: 0
… grid row overflow Root cause: Rust measure_mediawiki_special_generated hardcoded TITLE/SUBTITLE_LINE_HEIGHT=10.0px (measure.rs) while Java renders/relayouts at GuideText.lineHeight=17px, so node_type=30 box heights were systematically ~1/3 too small and the last grid row overflowed the box bottom (Special Index 'floating text', in-game too). Fix (diagnostician option A): fbs append-only link_line_height/subtitle_line_height (default 10.0 = old-data compatible); collectFontFactsImpl serializes GuideText.lineHeight(LINK/SUBTITLE_STYLE); Rust measure consumes serialized values, wrong constants deleted. Verified: Categories block h 237->335, all 6 rows inside box (ink below bottom 0); blocks without subtitles unchanged; gate TOTAL ISSUES 0; ratchet 33/33; reviewer ACCEPT (anti-pattern 4/4 clean). Earlier dead-path Java estimate unification stashed (superseded).
…emLink Inline item icons used a uniform 16px cell with zero text gap — icons appeared glued to following text (P6) regardless of actual sprite ink. - New IconMetrics: lazy static cache (item:meta), multi-pass merge, alpha>8 ink box; primary path getFrameTextureData, PNG resource fallback since runtime CPU frames are cleared after atlas upload (deviation: fallback added, all-fail -> null -> legacy 16px cell) - LytItemImage: tight advance for label-less inline path round(inkW*scale)+2*PAD (PAD=2) + draw offset -inkLeft*scale+PAD; label/block paths untouched Width evidence (headless bounds): crafting_table 16->20, compass 16->18, apple 32->28, diamond 64->52, emerald 64->48, gold_ingot 64->68; game-tags widths [28,62,72,105,96,64,20,18] all distinct. Visual gate TOTAL ISSUES 0, ratchet 33/33.
…t icon tofu ⓘ✦➤⚠☢ (U+24D8/2726/27A4/26A0/2622, Common script) are absent from msyh.ttc and have no system fallback path (fontique FallbackMap only tracks 7 scripts; the parley Hani-hack lands on msyh/SimSun which also lack them) -> .notdef tofu boxes in callout titles. - Rust: ParleyFonts::load_fallback_font_data registers fallback bytes, primes the system Hani fallback cache, then append_fallbacks (append, never set — preserves CJK fallback order); GuideFontSystem delegate; new JNI LayoutBridge_loadFallbackFont (init signature untouched) - Java: FontProvider default getFallbackFontData (empty); SystemFontProvider resolves seguisym.ttf (Windows) / Apple Symbols / DejaVu Sans candidates; call sites in GuideScreen.ensureLayout + RenderPageService.ensureFontEngineReady after init, before first layout; empty-skip + UnsatisfiedLinkError guard with logged warning (stale native lib tolerant) Evidence: fallback font load log (2514056 bytes seguisym); alert title run measured widths shift +1..3px (tofu -> real glyph); layout JSON byte-identical with/without fallback (no primary-glyph theft); geometric 0 findings, ratchet 33/33. Known deviation (executor-adjudicated): pre-existing identity-hash iteration-order fragility in guide layout — any Java-side call on the font init path deterministically flips some layout decisions (2-4px reflows, mermaid canvas arrangement); old build mermaid pages were already run-nondeterministic. Reflow-only, glyphs identical. Logged as tech debt for follow-up.
…la glyphs Two layered causes made inline formulas render far below body x-height (P1: '公式字体过小 + 基线未对齐'): 1. jlatexmath TeXIcon.setInsets(Insets) single-arg silently ADDS (int)(0.18f*size) per side — at size=100 the real inset is 20px/side, not the 2px every comment assumed. ~40px of vertical padding was baked into each texture, so the calibrated 'x-height' box was half padding: measured $x$ ink was 0.56x body x-height. 2. depth anchoring double-rounded (ceil source px, then round scaled), drifting the math baseline 1-2px. Round 1: INLINE_PERCEPTUAL_FACTOR=1.2f (TeX lowercase letters read small next to CJK body text at exact x-height parity); GuideLatexRenderer exposes TeXIcon.getBaseLine() exact ratio (baselineRatioCache + measureBaselineRatio); LytLatexBlock computes display depth in a single round; dead sourceDepthPx/LATEX_BOTTOM_INSET_PX removed. Round 2 (root fix): all 4 setInsets sites switched to the two-arg setInsets(insets, true) so insets are the intended 2px/side; javadocs document the single-arg pitfall. LytLatexDisplayBlock legacyPaddingDiff re-adds the removed phantom padding (2*(int)(0.18f*sourceScale)) to both size and refH — an algebraic identity (Fc+40)/(Xc+40) keeping the ratcheted display box ABI stable (11/11 display blocks 0% deviation) while textures now carry real ink. Evidence: $x$ ink 15px vs body x-height 12px = 1.25x (target 1.2); display blocks byte-identical dims; assert_bounds 35/35 (2 new h-floor assertions from measured values); geometric 0 findings; pixel diffs confined to formula regions (mermaid/nei/tasks/content-tabs exempt — known identity-hash nondeterminism).
ItemImage/ItemLink/FloatingImage/Image script error fallbacks render their red LytParagraph.error segment inline but occupied zero layout width: LayoutTreeSerializer's inline-block loop fell through to the else branch reading getBounds()=LytRect.empty() -> FloatAbs(0,0) -> Parley InlineBox width 0 -> pen never advanced, so the following body text overlapped the error text (12+ real corpus pages, e.g. alchemic_router 8 sibling intersections, electric_hoe 6). Add a LytParagraph branch before else: declare FloatAbs from GuideText.measureWidth (longest line) x lineHeight*lines (error paragraphs are not PRE; multi-line covered). Empty text or failed measurement falls back to the previous zero-width behavior with a debug log. One fix covers all script error fallbacks; LytImage and other dedicated branches untouched. Evidence: alchemic_router follow-up text x=39->494, dispensers list container w=26->322; geometric sibling_intersection 14->0 across the 5 affected pages; visualtest 23-page ratchet 35/35, geometric 0, pixel-identical outside known-nondeterministic pages.
…dotted Wavy and dotted underlines were rasterized by three hardcoded Java copies (main Tessellator path, legacy drawRect, VanillaRenderContext) drawing axis-aligned opaque 2x4px blocks per doc-pixel column — 8-phase sine rounded to integer offsets, 3x3 hard squares for dots, zero antialiasing (P4: '波浪线/下划虚线渲染太糙'). R1: new shared DecorationRasterizer (pure functions, no GL state) — subpixel sine sampling at 0.25 doc px with per-column coverage alpha, circular dot mask with soft edge falloff, run-merge for vertex bounding; all three call sites converged to it (wave: 2 discrete levels -> 19 brightness levels; dots: hard squares -> 243 core with 104/42 edge falloff). Geometry params unchanged (8px period, +/-2 amp, 2px thick, 4px pitch). R2 (review REJECT fixes): GL scope (pushAttrib GL_ENABLE_BIT + disable GL_ALPHA_TEST + try/finally pop) around wavy/dotted drawing on the two legacy paths — tooltip chain (GuideScreen ContentTooltip -> VanillaRenderContext) restores GL_GREATER 0.1 alpha test, which would cull alpha<=25 soft-edge fragments; alpha compositing corrected from coverage-replaces-tint-alpha to round(coverage*tintAlpha/255) (no-op at tint=255, documented in rasterizer javadoc). Evidence: 23/23 render ok, ratchet 35/35, geometric 0 findings; pixel diffs only in decorations + lists_tasks ^^wave^^ band (R1) and none on R2 (decorations page zero-diff vs R1 product); mermaid edges verified not routed through decoration kinds 4/5.
Real corpus uses FloatingImage with only width (or only height) on many
pages (toolbox, ore_stats, angelica, amount, fether, modern_tab,
baubles, coke_oven, naq_reactor...), but the compiler hard-rejected it
at parse time ('requires width or w, and height or h') — red error in
game and headless alike.
Single-dimension now means display-size semantics: full image (no
crop), given dimension = display px * scale, missing dimension inferred
from the natural aspect ratio (inferred = given * natural_other /
natural_given, no extra scale on the inferred axis). Two-dimension
crop semantics unchanged; natural dims <= 0 falls back to the previous
error path (which now reserves layout width thanks to the earlier
inline-error fix).
- FloatingImageCompiler: CropSpec has flags; exactly-one-dimension
legal, both-missing still errors with the original message
- Rust measure_image: aspect inference with divide-by-zero guard
- Java mirrors (LayoutTreeSerializer inline LytImage branch,
LytImage.computeLayout) carry the identical formula
- New fixture visualtest/images/single-param.md (width-only + height-only)
+ 3 ratchet assertions (38 total)
Evidence: 10 real-corpus images inferred within 1px rounding of natural
aspect; red error gone from render logs; images actually render (PIL
non-background content); floating-image.md 11 two-dimension blocks
pixel-exact (crop semantics untouched); 24/24 render ok, ratchet 38/38,
geometric 0, pixel-identical outside known-nondeterministic pages.
…or channel
Headless real-corpus renders showed scene viewports silently black
(subnetworks, crops_automation). Static hypothesis (modern SNBT dialect
unsupported) was falsified by probes: all corpus structures use the
legacy palette/blocks dialect and placed fine (76/90 blocks). The real
root cause was camera geometry:
- Explicit rotation center (centerY={-15}) skipped the auto-centering
translation, projecting the structure to screen y ~ -595 (offscreen)
- Ponder keyframe and GameScene offsets (offX/offY) were applied as
world units though documented/implemented everywhere else as screen
pixels (SceneTagCompiler comment, auto-pan writes screen px, ponder
drag accumulates screen px): -75/-90 * 10x zoom threw the structure
far out of view
Fixes:
- SceneScript.finalizeSceneGeometry: explicit-center scenes also get
structure centering (level center); offsets unified to screen px with
world conversion via s = 0.625*16*zoom = 10x zoom; dead center[] code
removed (explicit-center handled in applyCameraAndViewport)
- LytGuidebookScene: ponder camera offset unit unification; build()
errorSink=null replaced with a real scene error channel —
sceneBuildError recorded, warnAlways logged, drawSceneBuildError
draws red text; empty-level scenes get explicit feedback instead of
silent background (never silent black again)
- ImportStructureElementCompiler.placeStructure: modern dialect parsing
(string palette[] + data layers + string state) added alongside the
legacy path (zero behavior change, forward-compatible)
- wiki scene-camera.md (en+zh): stale 'units: blocks' corrected to
screen-space pixels + migration note (deployed resourcepack1 snapshot
intentionally untouched — follow-up redeploy item)
Evidence: subnetworks scene ROI non-background 7.13%->32.15%,
crops_automation 16.89%->23.56%, camera.md offset scene 9.68%->40.15%,
spatial-io offset scene 2.35%->9.75%; scenes/import fixture stable
(37.2%); 25/25 render ok, ratchet 38/38, geometric 0, pixel-identical
outside known-nondeterministic pages; 8-file affected-offset inventory
recorded.
…mpletes MediaWikiSpecialDataIndex warms up asynchronously on a daemon executor, but the headless batch renderer started immediately — alphabetically early Special pages (data-index driven) rendered as 96px empty fallback (13 victim pages in the 408-page real-corpus batch; warmup completed 1619ms AFTER page 12 had already rendered). GuideNhHeadlessRenderDriver.handleWorldStable now awaits warmup readiness before entering RENDERING (shared by batch and single-page modes): polls until specialDataIndex() is no longer the EMPTY singleton (reference comparison — fallback always uses the singleton, completed warmup installs a fresh instance), 25ms interval, 30s cap with warning, InterruptedException restores the interrupt flag. In-game path untouched (warmup long done before a user opens a page); the ~1s wait applies per headless render run regardless of MediaWiki content (documented minor cost). Evidence: changelog allPages 195/195 ok; all 13 race victims restored (allpages 48->954, categorytree 48->4521, doubleredirects 48->6078, globalfileusage 48->6231; 3 static pages unchanged as expected); log order 'warmup complete (964ms)' precedes 'Batch render start'; visualtest 25/25, ratchet 38/38, geometric 0, pixel diffs only in known-nondeterministic pages.
Map.copyOf produces JDK ImmutableCollections.MapN, which salts its hash table with System.nanoTime() per JVM — iteration order differs on every run. getNodes() order feeds both computeNodeMinSizes and the ELK layered layout's node creation order; ELK's crossing minimization keeps input order among equal solutions, so flowchart node placement drifted run-to-run (and any change to class-init/allocation order, e.g. an empty method call on the font init path, deterministically flipped some layouts — the P5-B anomaly). FlowchartDocument now wraps a LinkedHashMap (parse insertion order) with Collections.unmodifiableMap — same immutability, deterministic iteration. Diagnosis table: 6 other collections (IdentityHashMap caches, Integer-keyed HashMaps, List.copyOf) verified harmless (lookup-only or order-stable). Evidence: 25 pages rendered twice in independent JVM runs — 24/25 pixel-identical including ALL mermaid pages, tasks, content-tabs (the exemption list shrinks to nei_recipes only: its residual 1344px lives in the external GTNH sprite-atlas registration order, unreachable from this project); ratchet 38/38; geometric 0; text pages zero diff vs pre-fix baseline (mermaid pages allowed one-time baseline migration).
GuideNavBar was the last GUI chrome island on MC FontRenderer bitmap fonts (user directive: sidebar integration). All text now goes through the GuideText/Rust pipeline following the drawPageTitle precedent: title, row titles, and hover-scroll titles are emitted via GuideText.emitText into a PrimitiveCollector and executed once at the end of render() (legacy rects/icons/scissor draw first; execute() restores GL state and disables scissor so it must come last). - Row style fontScale 0.70 (lineHeight 12 == ROW_H 12), title 0.80 (lineHeight 14 in TITLE_H 16); vertical centering derived from lineHeight+ascent (measured ascent 12.0; row baseline rowY+8.4, CJK ink verified within [2,9] of the 12px row) - Colors via ConstantColor from getRowTextColor, shadow=false, matching previous drawString semantics; truncation via clipToWidth with ellipsis counted in the width budget - Hover smooth scroll preserved with pushScreenScissor/popScreenScissor primitives (single execute kept) - FontRenderer path retained as the GuideText.isAvailable()==false fallback (init-failure window) for every text element - executeNavText does an explicit beginFrame with DisplayScale.scaleFactor and clears stale body scissor before executing GuideScreen untouched; GuideNavProjection/State/GuidebookText untouched; mini item icons unchanged (not text). Headless page renders do not include GUI chrome, so runtime verification here is compile + static review; a dedicated headless chrome pass (S2) follows for pixel evidence. Regression: 25/25 render ok, ratchet 38/38, geometric 0, 24/25 pixel-identical (nei_recipes exempt — external atlas nondeterminism). Known follow-ups (reviewer-noted, non-blocking): sticky-row text z-order in scrolled state now composites above sticky rects (single execute consequence); navbar font metrics are msyh-measured.
…ication The S1 navbar migration had no runtime coverage: headless renders only collect document primitives, never the GUI chrome. Add an opt-in -Dguidenh.renderpage.chrome=true switch (build.gradle.kts forwards the key) that appends a navbar primitive pass: - RenderPageService: chrome=true renders the document independently (byte-identical path to chrome=false), then collects navbar primitives and composes side-by-side (nav left 162 logical px mirroring resolveNavigationOpenWidth 18%/min-110, document shifted right; output width width+navW; document bounds JSON untouched) - GuideNavBar.collectPrimitives: headless-friendly collection entry reusing the S1 text path (renderRowTitle -> GuideText.emitText), geometry as GradientFill/FillRect primitives, body clip via document-space pushScissor (scales with offscreen density); live render() untouched - GuideNhHeadlessRenderDriver: chrome config parse + passthrough + startup log Verified with the canonical gradlew -D command (no JAVA_TOOL_OPTIONS): chrome pass composed 79 nav primitives into 2124x1622 output; navbar band + separator present; text ink smoothly antialiased (vector glyphs, not bitmap); row pitch 24px at scale 2 == ROW_H 12; row-by-row projection log matches; chrome-vs-plain same-page document bounds 78 blocks byte-identical; chrome=false regression 25/25, ratchet 38/38, geometric 0, 24/25 pixel-identical (nei exempt). Tech debt (reviewer-noted): >100 lines of mirrored geometry between collectPrimitives and render() (arrow quads, row highlight colors) — share a geometry data source in a follow-up.
…gression Coverage-matrix gaps closed with 9 new fixture pages (syntax verified against each tag compiler, positions collision-free): - P0 zero-coverage: tags/block-stats.md (visible/buttonEnabled — the only wired attrs), tags/recipes-usage.md, tags/quest-link.md, mdx/file-tree.md, text/mark.md - P1 thin-coverage: text/item-link-dense.md (dense ItemLink mixes), text/color-named.md (16 symbolic colors; 3-digit hex confirmed malformed by the parser, 6-digit used), layout/callout-combos.md - F-N2 regression lock: text/error-fallback.md — ItemLink/ItemImage to nonexistent items followed by body text; ratchet pins all paragraphs w>=200 (zero-width collapse would fail); blind spot (error node disappearing entirely) documented in the fixture INVARIANTS 17 new ratchet assertions appended by hand (38 -> 55, strictly append-only, zero modifications to existing entries). 34/34 render ok, 55/55 assertions ok, geometric 0 findings, pixel regression 24/25 zero-diff (nei_recipes exempt). Documented engine behaviors found during authoring: [!NOTE] marker is swallowed by the parser (alert detection keys on body first word); title=/color-only quote directives fall through to the plain blockquote path (LytVBox, no header row) — fixture prose records actual behavior.
…rity
Follow-ups bringing the static site export in line with the in-game
pipeline:
- GuideSiteLatexExporter: setInsets switched to the two-arg form (the
single-arg overload silently adds (int)(0.18f*size) per side — 20px
instead of 2px at size 100), same fix as the runtime P1 change;
pitfall documented in comments
- GuideSiteHtmlCompiler FloatingImage: single-dimension usage now
legal (matches runtime F-N1): x/y optional (default 0), at least one
of width/height required, missing axis left to the browser's
intrinsic aspect ratio (export side cannot read PNG bytes —
ImageResolver only returns URLs; class-less inline-style structure
avoids app.js layoutCroppedFloatingImage overriding the given
dimension); both-missing still errors with the runtime-aligned
message; malformed/non-positive dimensions now error by attribute
PRESENCE (hasText) instead of silently falling back to single-param
mode ('must be an integer' / 'must be positive' texts aligned with
FloatingImageCompiler); two-dimension crop path output unchanged
- wiki Images-And-Assets + Tags-Reference (en/zh): parameter contract
updated to the new semantics
Verified: compile ok; probe 13 cases (malformed/zero/valid-single/
both-missing/alias-conflict) mismatches=0; headless Chromium bboxes
128x32 / 32x128 match fixture invariants; 34/34 render, 55/55 ratchet,
geometric 0, pixel diffs only in nei_recipes (exempt). Known limits:
single-param regional ImageAnnotations are not laid out by site JS
(app.js selector mismatch, documented); no headless export entry
exists (probe-driven verification instead).
…ng closure X.1 P1-P6 all closed (latex insets/perceptual factor, IconMetrics optical advance, special-index line heights, DecorationRasterizer AA, fallback fonts) X.2 F-N1/N2/N3 closed (single-dim images, error-fallback width, scene camera) X.3 infra: warmup race, MapN identity-hash, SideBar S1+S2 chrome pass X.4 9 adjudications: zero real bugs, homoglyph-run FP pattern registered X.5 follow-ups ledger: redeploy, navbar tech debt, deferred items, ratchet 55
INLINE_OPTICAL_PAD 2->0 (shared across computeContentSize/computeLayout/ renderX); computeLayout no-label inline branch aligned to tightW, removing the hidden 16px-vs-tightW advance inconsistency. Post-kerning gap on block icon rows eliminated (leading/trailingCell 3->1, cell 16).
P5-C: register seguisym as leading generic Emoji family so warning/caution glyphs (emoji-classified by parley) resolve to seguisym instead of Segoe UI Emoji COLR composites that collapse to fragments at 22px. F4: rasterize_out_glyphs clamps oversize raw_size to MAX_RASTER_SIZE=128 (was flooding the glyph atlas with 966-1517ppem bitmaps); non-finite or <=0 raw_size (fontScale=0 injection) falls back to DEFAULT_RASTER_SIZE=22 instead of rendering raw outline coordinates. Diagnostic WARN retained.
…l injection Ghosting root (live): regular-row y (scrolls) vs sticky-row y (pinned near body top) used two independent y sources with no screen-space avoidance; scrolled rows passing through sticky bands drew same-position text. Add overlapsStickyRow band-intersection skip in both mirror loops (live render and headless collectPrimitives), stickyRowY Math.max(bodyY,...) lower bound (closes sticky z-order item). Headless scrollY injection via -Dguidenh.renderpage.navscroll (default byte-identical) enables scrolled- state regression renders; build.gradle.kts forwards the property.
drawPageTitle titleX lacked the navbar reserved width (59cd3e2 predates sidebar integration) - title rendered inside the navbar column in live. titleX now adds getNavigationReservedWidth() and availableW subtracts it (right edge unchanged); mirrors recomputePanelBounds baseContentX.
…escape F7b-2/3: Block Stats name/count and load-state text migrated from MC pixel font (VanillaRenderContext.drawString) to engine vector font via GuideText.emitText in collectPrimitives; legacy drawText gated by suppress flags mirroring F7b-1; layout geometry extracted to context-free shared methods. Fixture: code/blocks.md L113 raw <>&"§ HTML-entity escaped (no longer triggers MDX parse error; heading content unchanged).
Lightweight whole-image query (--files + --prompt, free-text answer plus best-effort findings extraction) complementing vlm coarse screening; model name docs corrected to qwen3-vl-plus.
…olchain X.6: six-report fix wave 1-3 + regression closure. X.7: user re-verification (1 title NOT-FIXED, ItemLink kerning NOT-FIXED, Latex stretch SUSPECTED). F4: glyph-atlas flood root cause (fontScale=0 -> size=0 bypassed clamp -> outline-space bitmaps), bleed-stop + fallback fix, zoom-collapse root (visualZoom uninitialized). PF34: parallel background agents x headless renders = native OOM - render is a shared resource, lock protocol. WORKFLOW: ask toolchain reference.
…fect fixes T1: -Dguidenh.renderpage.mermaidzoom/mermaidoffset injection (RenderPageService applyMermaidInjection -> setHeadlessInjection, HEADLESS branch honors injection else historical fitZoom byte-identical); updateVisualState() per-frame driver at computePrimitives entry (raw zoom/offset -> visual values, wheel-zoom + drag now render); scaled-style cache invalidated on zoom change (fontScale follows zoom). Wave: A edge-label wrap budget = edge path length (was node gap 20px -> per-char vertical stack; R3-13 no-overlap preserved); B shape minNodeRect/contentBounds zoom-consistent (NODE_SIZE_ROUNDING_MARGIN, contentBounds(zoom) across 14 shapes, SubprocessShape frame scaled, Cloud/Circle ceil); C canvas explicit width clamped to available page width (100-node canvas 1709->480, dead-code clamp activated).
…ature pattern 1) Paging: MAX_PAGES=8 x 2048^2 (16MB/page, 128MB cap; was single 2048^2 drop-new- on-full) - 44ppem glyphs at render_scale=4 (6763-hanzi stress page + residue) previously overflowed 4 pages -> silent whole-page LRU eviction -> stored-run glyph loss. LRU per-page eviction (access-stamped, glDeleteTextures, never full clear - C-3 red line); failure set (structurally-unplaceable keys silent + one WARN, cleared on eviction); eviction WARN 1s-throttled (sentinel overflow fixed). Render: DrawGlyphRun atlasId removed, per-key page resolve + flush/rebind; tessColor-after-startDrawing regression fixed. Zoom quantization: getActiveZoom -> nearest 1.1^n (interactive/injected; no-inject fit path exempt, byte-identical md5 preserved) - animation no longer re-shapes/re-uploads per frame (jitter root removed). guiscale headless injection added (capped headless, registered).
…ics) + headless channels X.10 dual-defect fix: y overflow (HEADING1 line 26px in 16px toolbar -> painted over content; now TOOLBAR_TITLE_STYLE fontScale 0.8, line 14, in-band) and x alignment; then user verdict: plain title-bar semantics - title at toolbar left edge (panelX+PANEL_PADDING, no content-column offset), navbar confirmed below toolbar (navY=panelY+TOOLBAR_H+1) so no overlap; availableW no longer subtracts navReservedWidth. Headless channels: -Dguidenh.renderpage.title (drawPageTitle- equivalent path, default strict no-op, byte-identical md5) + guiscale injection; gradle forwards title/guiscale. Verified: title bbox x=8.0-8.5 (bearing), bottom 15.5<16, zero-change md5 e8430e19/1ac40ee4; reviewer double-pass ACCEPT.
…strations mermaid/ directions shapes links deep-subgraphs stress-large mindmap-variants rich-text node-content-tags mixed-multi (all non-stub rendered); stress/stress- glyphs.md (GB2312 6763 hanzi + 6985 unique chars + mermaid) - paging regression carrier. ISSUES.md X.8 (mermaid wave), X.9 (atlas paging), X.10 (title + capacity/ jitter), X.11 (typesetting red lines), X.12 (title fine-tune awaiting multimodal).
Windorain
marked this pull request as draft
August 7, 2026 16:08
…ine) display calibration merged into inline standard: displayScaleFactor = xHeight x 1.2 x userScale / (refH-4), W/H share one ratio (no stretch); legacyPaddingDiff removed (was compensating pre-fix baseline); constants promoted package-private, inline logic untouched. display/inline same- formula ratio 0.93 (1px = style icon diff), inline anchor byte-identical, latex ratchet 6/6. Site export kept (site formulas already shared).
Per-glyph cumulative advance compensation in layout emission (mature
synthetic-italic practice, equivalent to true-italic hmtx): x'_i = x_i +
sum_{j<i} K*(baseY - y_top_j), baseY = run max(q.y+q.h) same basis as
engine GLYPH_SHEAR_K=0.25 (cross-referenced); line-boundary reset;
SpanStyle.italic + measure passthrough; single-style italic paragraphs now
shear+compensate (base_italic, was always false). Upright zero-change
verified (md5 + JSON byte-identical); overhang eliminated (per-glyph shift
matches formula exactly). DLL rebuilt (3004f893).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refactor the layout and rendering system to provide a better visual experience
Provide headless rendering functionality for AI workflows and automated testing
Todo
Checklist