Close-packed crystals are stacks of identical hexagonal layers, and each layer can sit on only one of three lateral positions: A, or one of the two kinds of hollows B and C that the layer below offers (left). Face-centered cubic (fcc) metals stack them ABCABC, while hexagonal close-packed (hcp) metals stack ABAB. A stacking fault is a planar defect where this sequence breaks.
The four columns show the layers edge-on, each shifted sideways by its site so that the gray line tracing one atom up the stack climbs a straight slope in perfect fcc and kinks at every fault. Frank's notation writes each step as △ when it advances A→B→C→A and ▽ when it reverses. An intrinsic fault removes one layer (ABCA·CABC, a single ▽); an extrinsic fault inserts one (ABCA·C·BCA, two ▽ in a row); a twin boundary mirrors the stacking (ABCAB·ACBA, ▽ from there on). Shaded layers have the same site above and below, so they are locally hcp. A fault's excess energy per area, the stacking-fault energy γ, comes mostly from these hcp-like layers, so intrinsic and extrinsic faults (two each) cost about γ and a twin boundary (one) about γ/2.
An intrinsic fault is what a Shockley partial dislocation leaves behind when it glides through the crystal, so the stacking-fault energy sets how far a dissociated dislocation's two partials spread apart. That spacing controls cross slip, work hardening and twinning, which is why low-stacking-fault-energy alloys such as brass and austenitic steel deform so differently from aluminum.
#import "@preview/cetz:0.5.2": canvas, draw
#import draw: circle, content, line, rect
#set page(width: auto, height: auto, margin: 8pt, fill: none)
#set text(size: 12pt)
// Close-packed layers sit on one of three lateral positions A, B, C.
#let site-names = ("A", "B", "C")
#let site-colors = (rgb("#4c72b0"), rgb("#dd8452"), rgb("#55a868"))
#let hcp-fill = rgb("#d9d0ec")
// Secondary text stays dark enough that the site's dark-mode inversion turns it light gray.
#let muted = rgb("#404040")
// Each stacking step either advances A→B→C→A (△) or reverses it (▽), Frank's notation.
// Perfect fcc is all Δ; an intrinsic fault (a removed layer) flips one step, an extrinsic
// fault (an inserted layer) flips two in a row, and a twin boundary flips every step above it.
#let stackings = (
([perfect fcc], [ABC repeats], (1, 1, 1, 1, 1, 1, 1, 1)),
([intrinsic fault], [one layer removed], (1, 1, 1, -1, 1, 1, 1, 1)),
([extrinsic fault], [one layer inserted], (1, 1, 1, -1, -1, 1, 1, 1)),
([twin boundary], [mirror plane], (1, 1, 1, 1, -1, -1, -1, -1)),
)
#let (advance, reverse) = (sym.triangle.stroked.t, sym.triangle.stroked.b)
// Lateral shift of every layer in thirds of the atom spacing, bottom to top; its remainder
// mod 3 is the layer's site A, B or C.
#let layer-shifts(steps) = steps.fold((0,), (shifts, step) => shifts + (shifts.last() + step,))
// A layer whose neighbors above and below share a site (ABA) is locally hcp.
#let hcp-like(sites) = range(1, sites.len() - 1).filter(idx => (
sites.at(idx - 1) == sites.at(idx + 1)
))
// Flat discs with a darker rim: a lit-sphere highlight would invert into a dark pit under the
// site's dark-mode filter.
#let atom(pos, radius, fill) = circle(
pos,
radius: radius,
fill: fill,
stroke: fill.darken(35%) + 0.8pt,
)
#canvas({
// === Top view: one close-packed A layer and the two kinds of hollow above it ===
let polar(angle, dist) = (dist * calc.cos(angle), dist * calc.sin(angle))
for pos in ((0, 0),) + range(6).map(idx => polar(idx * 60deg, 1)) {
atom(pos, 0.5, site-colors.at(0))
}
// Hollows alternate between B and C around every atom; the next layer fills one kind.
for idx in range(6) {
let site = 1 + calc.rem(idx, 2)
let pos = polar(30deg + idx * 60deg, 1 / calc.sqrt(3))
circle(pos, radius: 0.19, fill: site-colors.at(site).darken(30%), stroke: white + 0.8pt)
content(pos, text(fill: white, weight: "bold", size: 7pt, site-names.at(site)))
}
content((0, 0), text(fill: white, weight: "bold", [A]))
content((0, -1.75), align(center)[close-packed layer A \ with B and C hollows], anchor: "north")
// === Side views: each stacking seen edge-on, layers shifted by their site ===
let (layer-gap, atom-gap, column-gap, radius) = (0.62, 0.78, 4.3, 0.21)
let atoms-per-layer = 4
let width = (atoms-per-layer - 1 + 2 / 3) * atom-gap
// hcp bands span from the row letters to past the step marks
let (origin, band-left, band-right) = (3.2, -0.7, width + 0.4)
let layer-y(idx) = idx * layer-gap - 2.2
for (column-idx, (title, subtitle, steps)) in stackings.enumerate() {
let shifts = layer-shifts(steps)
let sites = shifts.map(shift => calc.rem-euclid(shift, 3))
let left = origin + column-idx * column-gap
for idx in hcp-like(sites) {
let y = layer-y(idx)
rect(
(left + band-left, y - layer-gap / 2),
(left + band-right, y + layer-gap / 2),
fill: hcp-fill,
stroke: none,
)
}
// Following one atom up the stack: fcc climbs a straight slope, each fault kinks it.
line(
..shifts.enumerate().map(((idx, shift)) => (left + shift / 3 * atom-gap, layer-y(idx))),
stroke: (paint: rgb("#6b6b6b"), thickness: 2.5pt, cap: "round", join: "round"),
)
for (idx, site) in sites.enumerate() {
let y = layer-y(idx)
for atom-idx in range(atoms-per-layer) {
atom((left + (atom-idx + site / 3) * atom-gap, y), radius, site-colors.at(site))
}
content((left - 0.5, y), text(
fill: site-colors.at(site).darken(45%),
weight: "bold",
site-names.at(site),
))
}
for (idx, step) in steps.enumerate() {
let symbol = if step > 0 { advance } else { reverse }
content((left + width + 0.22, layer-y(idx + 0.5)), text(fill: muted, symbol))
}
let title-y = layer-y(sites.len() - 1) + 0.4
content(
(left + width / 2, title-y),
align(center)[*#title* \ #text(size: 10pt, fill: muted, subtitle)],
anchor: "south",
)
}
// === Legend ===
let legend-y = layer-y(0) - 0.65
// An inline swatch sits on the text baseline, so it lines up with the label like a glyph.
let swatch = box(width: 1.4em, height: 0.75em, fill: hcp-fill)
content(
(origin + band-left, legend-y),
[#swatch #h(0.3em) locally hcp layer (same site above and below)],
anchor: "west",
)
let frank = [#advance step A→B→C→A #h(0.8em) #reverse reversed step]
content((origin + 3 * column-gap + band-right, legend-y), frank, anchor: "east")
})