Exercise 3: Reproducing the Overlap Bug and the Exact Reordering Fix — Possible Solution ==================================================================== THE TEST ------------------------------ b = block({'background-color': 'blue', 'width': '100px', 'height': '50px'}) a = block({'background-color': 'red'}, children=[b]) layout_block(a, root_cb) # root_cb content width = 300 naive_list = build_display_list_naive(a) fixed_list = build_display_list(a) color_at_point(naive_list, 50, 25) color_at_point(fixed_list, 50, 25) RESULT ------------------------------ color_at_point(naive_list, 50, 25) -> 'red' (WRONG -- hides B entirely) color_at_point(fixed_list, 50, 25) -> 'blue' (correct -- B visible on top) THE EXACT TWO-LINE REORDERING ------------------------------ build_display_list_naive: for child in layout_box.children: build_display_list_naive(child, display_list) paint_background(layout_box, display_list) build_display_list (fixed): paint_background(layout_box, display_list) for child in layout_box.children: build_display_list(child, display_list) The two functions contain the exact same two statements -- a call to paint_background(layout_box, display_list) and a for-loop recursing into layout_box.children -- just written in the OPPOSITE order. In the naive version, the for-loop runs first, so every one of A's own children's paint commands (here, just B's blue rect) get appended to display_list BEFORE A's own paint_background call ever runs. In the fixed version, paint_background(layout_box, ...) is the very first statement in the function body, so A's own red rect is appended before the for-loop has a chance to recurse into B at all. WHY THE ORDER OF APPENDING DETERMINES WHAT color_at_point SEES ------------------------------ color_at_point walks the display list front-to-back and keeps overwriting `result` with each successive matching command's own color -- so whichever command's own rect happens to be LAST in the list, among those covering the queried point, wins. At (50, 25), BOTH A's rect (0,0,300,50) and B's rect (0,0,100,50) contain the point. In the naive list, ['blue', 'red'], 'blue' is checked first and sets result='blue', but then 'red' is checked and OVERWRITES it to 'red' -- since it appears later. In the fixed list, ['red', 'blue'], the reverse happens: 'red' is set first, then overwritten to 'blue'. The final winner is always whichever color's own command sits later in the list -- which is exactly why swapping the ORDER two statements run in is sufficient, on its own, to flip which color is visible at an overlapping point, without changing either statement's own logic at all. WHY THIS WORKS AS AN ANSWER ------------------------------ This confirms the bug was purely about SEQUENCE, not about any incorrect calculation -- paint_background() computes the exact same correct rect and color in both versions; the ONLY difference between the buggy and fixed function is which of two already-correct operations happens first. This is a useful, generalizable lesson: a display-list-style renderer's own correctness depends as much on COMMAND ORDER as it does on each individual command's own content -- a fact that becomes invisible if you only ever test commands one at a time rather than checking what actually happens when several of them overlap.