From fa8ce16936ca9a9ebdbaa1d71a103c10d5ab42bf Mon Sep 17 00:00:00 2001 From: Ilia Sharin Date: Tue, 12 May 2026 22:18:43 -0400 Subject: [PATCH] docs(amiga): fix README navigation indexes, add missing entries, and update Tier 3 statuses --- 05_reversing/README.md | 4 +- 09_intuition/gadgets.md | 409 +++++++++++++++++++++++++++++++++++++++- README.md | 5 +- TODO.md | 4 +- 4 files changed, 413 insertions(+), 9 deletions(-) diff --git a/05_reversing/README.md b/05_reversing/README.md index 5539276..761f25a 100644 --- a/05_reversing/README.md +++ b/05_reversing/README.md @@ -20,13 +20,13 @@ This section provides a systematic methodology for reverse engineering AmigaOS e | [ida_setup.md](ida_setup.md) | IDA Pro configuration for 68k/Amiga analysis | | [ghidra_setup.md](ghidra_setup.md) | Ghidra configuration for 68k/Amiga analysis & decompilation | | [compiler_fingerprints.md](compiler_fingerprints.md) | Compiler identification by code patterns | -| [library_reconstruction.md](library_reconstruction.md) | Reconstructing unknown library JMP tables | +| [static/library_jmp_table.md](static/library_jmp_table.md) | Reconstructing unknown library JMP tables and LVOs | | [static/code_vs_data_disambiguation.md](static/code_vs_data_disambiguation.md) | Distinguishing code bytes from data — IDA/Ghidra workflows | | [patching_techniques.md](patching_techniques.md) | Surgical binary patching methods | | [unpacking_and_decrunching.md](unpacking_and_decrunching.md) | Executable unpacking, decruncher architecture, and manual extraction | | [custom_loaders_and_drm.md](custom_loaders_and_drm.md) | Bypassing DOS, Trackloaders, and physical DRM tricks | | [anti_debugging.md](anti_debugging.md) | The Cracker vs. Developer arms race: Trace vector abuse, NMI defeat, CIA timers | -| [whdload_architecture.md](whdload_architecture.md) | WHDLoad internals, slaves, resload_DiskLoad, and runtime memory patching | +| [games/whdload_architecture.md](games/whdload_architecture.md) | WHDLoad internals, slaves, resload_DiskLoad, and runtime memory patching | | [case_studies/](case_studies/) | Real-world RE walkthroughs | | [case_studies/ramdrive_device.md](case_studies/ramdrive_device.md) | ramdrive.device RE walkthrough | diff --git a/09_intuition/gadgets.md b/09_intuition/gadgets.md index 6e4be34..4bf51a4 100644 --- a/09_intuition/gadgets.md +++ b/09_intuition/gadgets.md @@ -392,12 +392,413 @@ The `MaxChars` field includes the null terminator. If your buffer is 64 bytes, s 6. **Set `PLACETEXT_LEFT/RIGHT/ABOVE`** for label placement — don't hardcode text positions 7. **Handle `IDCMP_REFRESHWINDOW`** with `GT_BeginRefresh()`/`GT_EndRefresh()` 8. **Give every gadget a unique `GadgetID`** — this is how you identify the source in `IDCMP_GADGETUP` +9. **Always check `CreateGadget()` return** — a NULL return means the list is broken; you must free everything +10. **Create GadTools gadgets in order** — the linked list must be contiguous; skipping one breaks the chain +11. **Use `GTMN_NewLookMenus, TRUE`** when calling `CreateMenus()` — consistent 3D look +12. **Use `GA_Immediate` / `GA_RelVerify`** on raw gadgets — without both, you miss press/release events + +--- + +## Named Antipatterns + +### "The Invisible Gadget" — Forgetting GT_RefreshWindow + +```c +/* BAD: GadTools gadgets need an explicit render pass after window open. + Without it, the gadget structures exist but nothing is drawn. */ +struct Window *win = OpenWindowTags(NULL, + WA_Gadgets, glist, + TAG_DONE); +/* User sees an empty window — gadgets are invisible! */ +``` + +```c +/* CORRECT: Always call GT_RefreshWindow after opening */ +struct Window *win = OpenWindowTags(NULL, + WA_Gadgets, glist, + TAG_DONE); +GT_RefreshWindow(win, NULL); /* renders all gadget borders and labels */ +``` + +### "The Message Mangler" — Mixing GetMsg with GadTools + +```c +/* BAD: Using exec.library GetMsg() instead of GT_GetIMsg(). + GadTools internally injects messages for sub-events (ListView scrolling, + slider dragging). GetMsg() doesn't route through GadTools' filter, + so internal state desynchronizes — gadgets stop responding. */ +struct IntuiMessage *msg; +while ((msg = GetMsg(win->UserPort))) /* WRONG */ +{ + HandleMessage(msg); + ReplyMsg(msg); /* ALSO WRONG — must use GT_ReplyIMsg */ +} +``` + +```c +/* CORRECT: Always use the GT_ pair */ +struct IntuiMessage *msg; +while ((msg = GT_GetIMsg(win->UserPort))) +{ + HandleMessage(msg); + GT_ReplyIMsg(msg); +} +``` + +### "The Orphaned Chain" — Not Checking CreateGadget Return + +```c +/* BAD: If CreateGadget() returns NULL, the linked list is broken. + Continuing to use 'gad' as context for subsequent calls creates + a corrupted gadget list — crash or silent malfunction. */ +gad = CreateGadget(BUTTON_KIND, gad, &ng, TAG_DONE); +/* No NULL check! If this failed, gad is NULL... */ +gad = CreateGadget(STRING_KIND, gad, &ng, TAG_DONE); /* undefined behavior */ +``` + +```c +/* CORRECT: Check every CreateGadget return */ +gad = CreateGadget(BUTTON_KIND, gad, &ng, TAG_DONE); +if (!gad) goto cleanup; + +gad = CreateGadget(STRING_KIND, gad, &ng, TAG_DONE); +if (!gad) goto cleanup; +/* ... */ +cleanup: + FreeGadgets(glist); /* frees everything created so far */ +``` + +### "The Hot-Swap Hazard" — Modifying Gadgets In-Place + +```c +/* BAD: Changing a raw gadget's position while it's attached. + Intuition may be in the middle of rendering the gadget when + you change its coordinates — screen corruption. */ +myGadget->LeftEdge = 50; /* LIVE MODIFICATION — DANGEROUS */ +myGadget->Width = 200; +``` + +```c +/* CORRECT: Remove → modify → re-add → refresh */ +UWORD pos = RemoveGadget(win, myGadget); +myGadget->LeftEdge = 50; +myGadget->Width = 200; +AddGadget(win, myGadget, pos); +RefreshGList(myGadget, win, NULL, 1); +``` + +> [!TIP] +> For GadTools gadgets, use `GT_SetGadgetAttrs()` instead — it handles the remove/modify/add/refresh cycle internally. + +### "The Silent Button" — Missing GACT_RELVERIFY + +```c +/* BAD: Without GACT_RELVERIFY, Intuition never sends IDCMP_GADGETUP. + The user clicks the button, sees it highlight, but nothing happens. */ +struct Gadget myButton = { + .Flags = GFLG_GADGHCOMP, + .Activation = GACT_IMMEDIATE, /* only sends GADGETDOWN, not GADGETUP */ + .GadgetType = GTYP_BOOLGADGET, +}; +``` + +```c +/* CORRECT: Set both IMMEDIATE and RELVERIFY */ +struct Gadget myButton = { + .Flags = GFLG_GADGHCOMP, + .Activation = GACT_IMMEDIATE | GACT_RELVERIFY, + .GadgetType = GTYP_BOOLGADGET, +}; +/* Now you get both IDCMP_GADGETDOWN (press) and IDCMP_GADGETUP (release) */ +``` + +--- + +## GadTools → BOOPSI Migration Guide + +GadTools is built on top of raw `struct Gadget`. BOOPSI gadgets (`GTYP_CUSTOMGADGET`) are a different system. Here's when and how to migrate: + +### When to Stick with GadTools + +- Standard buttons, checkboxes, sliders, string fields +- Application targets OS 2.0–3.1 +- No custom gadget rendering needed +- Quick prototyping + +### When to Move to BOOPSI + +- Custom gadget appearance or behavior +- Inter-gadget communication (slider auto-updates label) +- Reusable components across projects +- Need `layouthook`-based auto-positioning + +### Quick-Reference: Same Gadget, Two APIs + +| Task | GadTools | BOOPSI (ReAction/MUI) | +|------|----------|-----------------------| +| Create button | `CreateGadget(BUTTON_KIND, ...)` | `NewObject(NULL, "button.gadget", ...)` | +| Set value | `GT_SetGadgetAttrs(gad, win, NULL, TAG_DONE)` | `SetAttrs(obj, TAG_DONE)` | +| Get value | `GT_GetGadgetAttrs(gad, win, NULL, TAG_DONE)` | `GetAttr(attr, obj, &val)` | +| Disable | `GT_SetGadgetAttrs(gad, win, NULL, GA_Disabled, TRUE, TAG_DONE)` | `SetAttrs(obj, GA_Disabled, TRUE, TAG_DONE)` | +| Event | `IDCMP_GADGETUP`, check `GadgetID` | `IDCMP_GADGETUP`, check `GadgetID` or `GA_ID` | +| Free | `FreeGadgets(glist)` | `DisposeObject(obj)` | + +### Key Differences + +```c +/* GadTools: all gadgets share one linked list, created sequentially */ +gad = CreateContext(&glist); +gad = CreateGadget(BUTTON_KIND, gad, &ng1, TAG_DONE); +gad = CreateGadget(STRING_KIND, gad, &ng2, TAG_DONE); +/* glist points to the head — contains all gadgets */ + +/* BOOPSI: each gadget is an independent object */ +Object *btn = NewObject(NULL, "button.gadget", + GA_ID, 1, + TAG_DONE); +Object *str = NewObject(NULL, "strgadget", + GA_ID, 2, + TAG_DONE); +/* Must manually build into a gadget list or use a group */ +``` + +```mermaid +graph LR + subgraph "GadTools Approach" + CTX[CreateContext] --> G1[Button] + G1 --> G2[String] + G2 --> G3[Slider] + end + + subgraph "BOOPSI Approach" + B[NewObject button] + S[NewObject strgadget] + SL[NewObject propgadget] + B -->|GA_Next| S + S -->|GA_Next| SL + end +``` + +--- + +## Practical Cookbook: Form with Mixed Gadgets + +```c +#include +#include +#include +#include + +enum { + GAD_NAME = 1, + GAD_AGE, + GAD_COLOR, + GAD_OK, + GAD_CANCEL +}; + +static const STRPTR colorLabels[] = { "Red", "Green", "Blue", NULL }; + +struct AppGadgets { + struct Gadget *glist; + struct Gadget *gadName; + struct Gadget *gadAge; + struct Gadget *gadColor; + struct Gadget *gadOk; +}; + +struct AppGadgets CreateFormGadgets(APTR vi, struct TextAttr *topaz8) +{ + struct AppGadgets ag = {0}; + struct Gadget *gad; + struct NewGadget ng = {0}; + + gad = CreateContext(&ag.glist); + + /* --- Name string gadget --- */ + ng.ng_LeftEdge = 80; + ng.ng_TopEdge = 20; + ng.ng_Width = 200; + ng.ng_Height = 16; + ng.ng_GadgetText = "Name:"; + ng.ng_TextAttr = topaz8; + ng.ng_VisualInfo = vi; + ng.ng_Flags = PLACETEXT_LEFT; + + ag.gadName = gad = CreateGadget(STRING_KIND, gad, &ng, + GTST_MaxChars, 63, + GTST_String, "", + GA_ID, GAD_NAME, + TAG_DONE); + + /* --- Age integer gadget --- */ + ng.ng_TopEdge += 24; + ng.ng_GadgetText = "Age:"; + ng.ng_GadgetID = GAD_AGE; + + ag.gadAge = gad = CreateGadget(INTEGER_KIND, gad, &ng, + GTIN_MaxChars, 4, + GA_ID, GAD_AGE, + TAG_DONE); + + /* --- Color cycle gadget --- */ + ng.ng_TopEdge += 24; + ng.ng_Width = 200; + ng.ng_GadgetText = "Color:"; + ng.ng_GadgetID = GAD_COLOR; + + ag.gadColor = gad = CreateGadget(CYCLE_KIND, gad, &ng, + GTCY_Labels, colorLabels, + GA_ID, GAD_COLOR, + TAG_DONE); + + /* --- OK / Cancel buttons --- */ + ng.ng_TopEdge += 30; + ng.ng_LeftEdge = 120; + ng.ng_Width = 80; + ng.ng_Height = 20; + ng.ng_GadgetText = "_OK"; + ng.ng_Flags = PLACETEXT_IN; + + ag.gadOk = gad = CreateGadget(BUTTON_KIND, gad, &ng, + GA_ID, GAD_OK, + TAG_DONE); + + ng.ng_LeftEdge += 90; + ng.ng_GadgetText = "_Cancel"; + ng.ng_GadgetID = GAD_CANCEL; + + gad = CreateGadget(BUTTON_KIND, gad, &ng, + GA_ID, GAD_CANCEL, + TAG_DONE); + + return ag; +} + +void ReadFormValues(struct AppGadgets *ag, struct Window *win) +{ + STRPTR name; + LONG age; + USHORT colorIdx; + + GT_GetGadgetAttrs(ag->gadName, win, NULL, + GTST_String, &name, TAG_DONE); + + GT_GetGadgetAttrs(ag->gadAge, win, NULL, + GTIN_Number, &age, TAG_DONE); + + GT_GetGadgetAttrs(ag->gadColor, win, NULL, + GTCY_Active, &colorIdx, TAG_DONE); + + Printf("Name=%s Age=%ld Color=%s\n", + name, age, colorLabels[colorIdx]); +} +``` + +--- + +## Historical Context & Modern Analogies + +### Competitive Landscape + +| Platform | Gadget System | Custom Classes | Layout Engine | Inter-Gadget Communication | +|----------|-------------|---------------|--------------|--------------------------| +| **AmigaOS GadTools** | `CreateGadget()` + `NewGadget` | No — fixed gadget types | Manual coordinates | Via application event loop | +| **AmigaOS BOOPSI** | `NewObject()` + dispatcher | Yes — subclass rootclass | Manual or layout hook | ICA interconnection (observer) | +| **Amiga MUI** | `NewObject("mui.class")` | Yes — subclass MUI classes | Automatic (group/layout) | Notification hooks | +| **Mac OS (Classic)** | Control Manager + CNTL resources | Yes (via CDEF) | Manual or dialog resources | Application event routing | +| **Windows 3.x** | `CreateWindow()` + `WNDCLASS` | Yes (via WNDPROC) | Manual or dialog templates | `WM_COMMAND` / `SendDlgItemMessage()` | +| **X11/Motif** | `XtCreateWidget()` + `XmCreate*` | Yes (via Xt class) | `XmForm` constraints | `XtAddCallback()` | + +### Evolution of Amiga Gadget Systems + +```mermaid +timeline + title Amiga Gadget API Timeline + 1985 : OS 1.0 — Raw struct Gadget\nManual Border/Image imagery + 1987 : OS 1.3 — No significant changes\nGadgets remained primitive + 1990 : OS 2.0 — GadTools.library\nCreateGadget() API\nBOOPSI introduced + 1992 : OS 2.1 — gadtools V39\nGT_GetGadgetAttrs added + 1993 : OS 3.0 — NewLook 3D gadgets\nGA_Disabled appearance + 1995 : OS 3.1 — No API changes\nBug fixes only + 1996 : MUI 3.x — Third-party\nAutomatic layout, skinning + 1999 : ReAction — AROS/OS 3.5+\nWindow.class, Layout.gadget +``` + +### Modern Analogies + +| Amiga Concept | Modern Equivalent | Notes | +|--------------|-------------------|-------| +| `struct Gadget` | `GtkWidget` / `QWidget` / `NSView` | Base widget type | +| `GadgetID` | `gtk_buildable_get_name()` / Qt `objectName` | Widget identification | +| `CreateGadget(KIND, ...)` | `gtk_button_new_with_label()` / Qt `QPushButton(tr("..."))` | Factory creation | +| `GT_SetGadgetAttrs()` | `gtk_widget_set_property()` / Qt `setProperty()` | Runtime update | +| `GT_GetGadgetAttrs()` | `gtk_widget_get_property()` / Qt `property()` | Runtime query | +| `GA_Disabled` | `gtk_widget_set_sensitive()` / Qt `setEnabled()` | Gray out | +| `GFLG_RELWIDTH/RELHEIGHT` | GTK `hexpand` / Qt `sizePolicy` | Responsive sizing | +| `CreateContext(&glist)` | GtkBuilder `.ui` file / Qt `.ui` file | Container for widget tree | +| `GT_RefreshWindow()` | Not needed — modern toolkits auto-render | Amiga requires explicit refresh | +| `PropInfo` (HorizPot/VertPot) | `gtk_adjustment_set_value()` / Qt `QSlider::setValue()` | Scrollbar/slider state | +| `RemoveGadget()` / `AddGadget()` | `gtk_container_remove()` / `gtk_container_add()` | Dynamic widget management | +| `PLACETEXT_LEFT/RIGHT` | GTK `GtkLabel` + `GtkBox` / Qt `QFormLayout` | Label positioning | + +--- + +## Use Cases + +| Application | Gadget Types Used | Notable Patterns | +|-------------|------------------|-----------------| +| **Workbench** | System gadgets (close, depth, drag, size) | All windows use the same system gadget set | +| **Prefs programs** | Cycle, slider, palette, button | Cycle for screen mode, slider for frequency, palette for colors | +| **File requesters (ASL)** | ListView, string, button, scroller | ListView for file list, string for filename input | +| **Text editors** | String, button, scroller | String for find/replace dialogs, scroller for document view | +| **Image editors (DPaint)** | Cycle, palette, button, custom gadgets | Palette for color selection, custom gadgets for tool options | +| **Audio trackers** | Integer, slider, button, listview | Integer for BPM/tempo, slider for volume, listview for patterns | +| **Installer scripts** | Button, checkbox, string | Checkbox for options, string for install path | +| **Game launchers** | Cycle, button, checkbox, slider | Cycle for resolution, slider for volume, checkbox for fullscreen | + +--- + +## FAQ + +**Q: What's the difference between GadTools and BOOPSI gadgets?** +A: GadTools is a convenience wrapper that creates raw `struct Gadget` objects with pre-built rendering and behavior. BOOPSI gadgets (`GTYP_CUSTOMGADGET`) use `DoMethod()` dispatch and support inheritance and ICA interconnection. GadTools gadgets are simpler but limited to fixed types; BOOPSI gadgets are extensible but require more boilerplate. + +**Q: Can I mix GadTools and BOOPSI gadgets in the same window?** +A: Yes — both ultimately become `struct Gadget` nodes in the same linked list. However, you must use the correct API for each type: `GT_SetGadgetAttrs()` for GadTools, `SetAttrs()` for BOOPSI. Event handling (`IDCMP_GADGETUP`) works the same for both. + +**Q: Why does my slider only send `IDCMP_GADGETUP` on release?** +A: By default, sliders only report the final value. To get continuous updates while dragging, set `GA_FollowMouse` in `CreateGadget()`. This causes `IDCMP_MOUSEMOVE` events with the slider gadget as `IAddress` while the user drags. + +**Q: How do I create a scrollable list that updates dynamically?** +A: Use `LISTVIEW_KIND` with `GT_SetGadgetAttrs()` and `GTLV_Labels` to update the string array. The listview redraws automatically. Remember the label array must remain valid (static or heap-allocated) for the lifetime of the gadget. + +**Q: Can gadgets span multiple windows?** +A: No — a gadget can only be attached to one window at a time. To create the same gadget in multiple windows, create separate gadget instances for each. + +**Q: What happens to gadget input during menu mode?** +A: Intuition freezes all gadget input while the menu strip is active (right mouse button held). No `IDCMP_GADGETDOWN`/`IDCMP_GADGETUP` events are delivered until the menu closes. --- ## References -- NDK 3.9: `intuition/intuition.h`, `intuition/gadgetclass.h`, `libraries/gadtools.h` -- ADCD 2.1: `CreateGadget()`, `GT_SetGadgetAttrs()`, `GT_GetGadgetAttrs()` -- AmigaOS Reference Manual (RKRM): Libraries, Chapter 7 — Gadgets -- See also: [BOOPSI](boopsi.md), [IDCMP](idcmp.md), [Windows](windows.md) +### NDK Headers + +- `intuition/intuition.h` — `struct Gadget`, `struct PropInfo`, `struct StringInfo`, `GFLG_*`/`GACT_*`/`GTYP_*` flags +- `intuition/gadgetclass.h` — BOOPSI gadget class attributes (`GA_*`) +- `libraries/gadtools.h` — `struct NewGadget`, gadget kinds (`*_KIND`), `GTST_*`/`GTSL_*`/`GTCY_*` tags + +### Autodocs + +- ADCD 2.1: `CreateGadget()`, `CreateContext()`, `GT_SetGadgetAttrs()`, `GT_GetGadgetAttrs()`, `FreeGadgets()` +- ADCD 2.1: `GT_GetIMsg()`, `GT_ReplyIMsg()`, `GT_RefreshWindow()`, `GT_BeginRefresh()`, `GT_EndRefresh()` +- ADCD 2.1: `AddGadget()`, `RemoveGadget()`, `RefreshGList()` + +### Related Knowledge Base Articles + +- [BOOPSI](boopsi.md) — object-oriented gadget system, ICA interconnection +- [IDCMP](idcmp.md) — `IDCMP_GADGETUP`/`IDCMP_GADGETDOWN` event handling +- [Windows](windows.md) — windows host gadget lists +- [Screens](screens.md) — VisualInfo ties gadgets to screen appearance +- [Intuition Base](intuition_base.md) — Intuition's global state and rendering diff --git a/README.md b/README.md index b6a3094..014a678 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post | [unpacking_and_decrunching.md](05_reversing/unpacking_and_decrunching.md) | Executable unpacking, decruncher architecture, and manual extraction | | [custom_loaders_and_drm.md](05_reversing/custom_loaders_and_drm.md) | Bypassing DOS, Trackloaders, and physical DRM tricks | | [anti_debugging.md](05_reversing/anti_debugging.md) | The Cracker vs. Developer arms race: Trace vector abuse, NMI defeat | -| [whdload_architecture.md](05_reversing/whdload_architecture.md) | WHDLoad internals, slaves, resload_DiskLoad, memory patching | +| [games/whdload_architecture.md](05_reversing/games/whdload_architecture.md) | WHDLoad internals, slaves, resload_DiskLoad, memory patching | | [case_studies/ramdrive_device.md](05_reversing/case_studies/ramdrive_device.md) | Case Study: ramdrive.device RE walkthrough | | Per-Compiler RE Field Manuals | Topic | @@ -213,6 +213,8 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post | [blitter.md](08_graphics/blitter.md) | Blitter DMA, minterms, BltBitMap | | [blitter_programming.md](08_graphics/blitter_programming.md) | Blitter deep dive: cookie-cut, fill, line draw | | [sprites.md](08_graphics/sprites.md) | Hardware sprites, SimpleSprite | +| [display_modes.md](08_graphics/display_modes.md) | ModeID selection flowchart, CRT vs flat-panel, interlace/progressive tradeoffs, named antipatterns, FPGA/MiSTer impact, historical context, modern analogies, FAQ | +| [ham_ehb_modes.md](08_graphics/ham_ehb_modes.md) | HAM6/HAM8 encoding pipeline, EHB half-brite, fringing, palette programming, FPGA decoder logic | | [rastport.md](08_graphics/rastport.md) | RastPort, drawing primitives, layers | | [views.md](08_graphics/views.md) | View/ViewPort, MakeVPort, display pipeline | | [text_fonts.md](08_graphics/text_fonts.md) | TextFont bitmap layout, baseline rendering, algorithmic styles, AvailFonts enumeration | @@ -233,6 +235,7 @@ The Amiga's documentation was scattered across out-of-print manuals, Usenet post | [boopsi.md](09_intuition/boopsi.md) | BOOPSI object system, custom classes | | [input_events.md](09_intuition/input_events.md) | InputEvent, Commodities Exchange | | [commodities.md](09_intuition/commodities.md) | Commodities Exchange: hotkeys, screen blankers, CxObject API | +| **[frameworks/mui/](09_intuition/frameworks/mui/)** | **MUI (Magic User Interface): architecture, layout system, widgets, custom classes, events, reference snippets** | ### 10 — Devices | File | Topic | diff --git a/TODO.md b/TODO.md index 952b266..c13ea98 100644 --- a/TODO.md +++ b/TODO.md @@ -57,7 +57,7 @@ Articles were scored against [AGENTS.md](../amiga/AGENTS.md) "Deep" criteria: | 17 | `09_intuition/screens.md` | 582 | 582 | ❌ **Pending** — Antipatterns, cookbook (screen flipping, borderless, PAL→NTSC handling) | | 18 | `09_intuition/windows.md` | 370 | 778 | ✅ **Complete** — Added 5 named antipatterns (Border Collision, Unresponsive Close, Leaked Message, Refresh Loop, Phantom Window), window type decision guide with Mermaid, 3 cookbooks (resizable, borderless overlay, multi-window shared port), historical comparison, modern analogies, use cases, 7 FAQ | | 19 | `09_intuition/menus.md` | 378 | 695 | ✅ **Complete** — Added render chain sequence diagram, 5 named antipatterns (Multi-Select Ghost, Stale Menu, Cleanup Reversal, Shortcut Collision, Phantom VisualInfo), complete lifecycle cookbook, historical comparison, modern analogies, use cases, 6 FAQ | -| 20 | `09_intuition/gadgets.md` | 403 | 403 | ❌ **Pending** — Named antipatterns, BOOPSI command flow, GadTools→BOOPSI migration guide | +| 20 | `09_intuition/gadgets.md` | 403 | 804 | ✅ **Complete** — Added 5 named antipatterns (Invisible Gadget, Message Mangler, Orphaned Chain, Hot-Swap Hazard, Silent Button), GadTools→BOOPSI migration guide with comparison table, form cookbook, historical timeline, modern analogies, use cases, 6 FAQ | | 21 | `11_libraries/iffparse.md` | 271 | 1031 | ✅ **DONE** — Nesting & chunk hierarchy diagrams, ILBM/EHB pitfalls, PBM read patterns, cross-reference to Datatypes | | 22 | `13_toolchain/gcc_amiga.md` | 82 | 82 | ❌ **Pending** — Extremely thin stub. Needs full build pipeline, Docker cross-compilation guide, linker scripts, amiga-gcc specifics | | 23 | `11_libraries/layers.md` | 224 | 739 | ✅ **Complete** — Expanded with ClipRect engine deep-dive, API reference with LVOs, backfill hook cookbook, refresh type decision guide, 4 named antipatterns, 4 pitfalls, ClipBlit vs ScrollRaster optimization, historical comparison table, modern analogies, 7 FAQ | @@ -269,7 +269,7 @@ Articles were scored against [AGENTS.md](../amiga/AGENTS.md) "Deep" criteria: | `commodities.md` | 769 | ✅ Deep | Tier 1 creation: Exchange, hotkeys, CxObjects, brokers | | `screens.md` | 582 | ❌ Pending | Tier 3 #17: needs antipatterns, cookbook (screen flipping, borderless, PAL→NTSC) | | `boopsi.md` | 505 | ✅ Adequate | OOP dispatcher, ICA interconnection, custom class tutorial | -| `gadgets.md` | 403 | ❌ Pending | Tier 3 #20: needs antipatterns, BOOPSI command flow, GadTools→BOOPSI guide | +| `gadgets.md` | 804 | ✅ Deep | 5 antipatterns, GadTools→BOOPSI migration, form cookbook, timeline, modern analogies, use cases, 6 FAQ | | `menus.md` | 695 | ✅ Deep | Render chain diagram, 5 antipatterns, lifecycle cookbook, historical + modern analogies, use cases, 6 FAQ | | `windows.md` | 778 | ✅ Deep | 5 named antipatterns, window type decision guide, 3 cookbooks (resizable, borderless, shared port), historical + modern analogies, use cases, 7 FAQ | | `requesters.md` | 370 | ✅ Adequate | EasyRequest, ASL file/font/screenmode dialogs |