diff --git a/content/quizzes/developer-holy-wars-measured.json b/content/quizzes/developer-holy-wars-measured.json new file mode 100644 index 0000000..544e450 --- /dev/null +++ b/content/quizzes/developer-holy-wars-measured.json @@ -0,0 +1,451 @@ +{ + "schemaVersion": 1, + "id": "developer-holy-wars-measured", + "title": "Tabs, spaces and Vim: developer holy wars, measured", + "description": "Developers have argued about tabs versus spaces and Vim versus Emacs for decades, and now and then someone collects data. Each Question takes one argument, shows what a survey, a traffic log or a tool's design measured, and asks what you can and can't conclude from it. You'll also learn how to quit Vim.", + "language": "en", + "tags": [ + "statistics", + "critical-thinking", + "work", + "internet-culture" + ], + "author": "Quizbun", + "questions": [ + { + "id": "tabs-or-spaces-median-salary", + "title": "In Stack Overflow's 2017 Developer Survey, which group of professional developers reported the higher median salary: tabs users or spaces users?", + "type": "single-choice", + "options": [ + { + "text": "Spaces users, by about $15,000 a year", + "isCorrect": true + }, + { + "text": "Tabs users, by about $15,000 a year", + "isCorrect": false + }, + { + "text": "Neither: the two medians were within $1,000 of each other", + "isCorrect": false + }, + { + "text": "The survey didn't ask about salary, so nobody knows", + "isCorrect": false + } + ], + "explanation": "Data scientist David Robinson compared the two groups in the 2017 survey data. Developers who indented with spaces reported a median salary of **$59,140**. Those who used tabs reported **$43,750**.\n\nA gap that big, between groups that differ by one keystroke, should make you suspicious. A raw median mixes everything together: country, experience, language and job type. If spaces were more common in higher-paying countries or languages, a gap would appear even if indentation had nothing to do with pay. The next Questions look at how Robinson tried to separate those effects.\n\nKeep in mind where the numbers come from. People reported their own salaries, in a survey they chose to take on Stack Overflow.", + "references": "[Stack Overflow Blog: Developers who use spaces make more money than those who use tabs (David Robinson, 2017)](https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/), [GitHub: dgrtwo/tabs-spaces-post (the analysis code)](https://github.com/dgrtwo/tabs-spaces-post)" + }, + { + "id": "tabs-spaces-both-split", + "title": "How did the 28,657 professional developers who answered the 2017 indentation question split between tabs, spaces and both?", + "type": "single-choice", + "options": [ + { + "text": "Close to a tie: about 41% spaces, 41% tabs, and 18% both", + "isCorrect": true + }, + { + "text": "Spaces won easily: about 80% spaces and 20% tabs", + "isCorrect": false + }, + { + "text": "Tabs won easily: about 70% tabs and 30% spaces", + "isCorrect": false + }, + { + "text": "About a third each for tabs, spaces and both", + "isCorrect": false + } + ], + "explanation": "The split was **41.8% spaces, 40.7% tabs and 17.5% both**. Neither side won, and the two camps were almost the same size.\n\nThat matters for the salary comparison. Both groups are large, so one small, unusual group can't explain the salary gap. Of the 28,657 who answered the indentation question, 12,426 also reported a salary, and the salary analysis rests on those people.\n\nThe \"both\" group shows how survey categories flatten real behavior. Many developers follow whatever the project's style guide or formatter says, which can mean tabs in one repository and spaces in the next.", + "references": "[Stack Overflow Blog: Developers who use spaces make more money than those who use tabs (David Robinson, 2017)](https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/)", + "images": [ + { + "src": "tabs-spaces-both-split.svg", + "alt": "Bar chart of the 2017 Stack Overflow survey question on indentation, 28,657 professional developers. Spaces 41.8%, tabs 40.7%, both 17.5%, also shown as a single stacked bar. A note says tabs and spaces are nearly tied and about one developer in six uses both.", + "caption": "Percentages from David Robinson's 2017 Stack Overflow analysis. Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 350 + } + ] + }, + { + "id": "what-robinson-controlled-for", + "title": "To test whether the salary gap was just a side effect of other differences, Robinson fitted a regression model. Which factors did it control for?", + "type": "multiple-choice", + "options": [ + { + "text": "Country", + "isCorrect": true + }, + { + "text": "Years of programming experience", + "isCorrect": true + }, + { + "text": "Programming language and type of developer", + "isCorrect": true + }, + { + "text": "Level of formal education", + "isCorrect": true + }, + { + "text": "Company size", + "isCorrect": true + }, + { + "text": "The editor or IDE each developer uses", + "isCorrect": false + }, + { + "text": "Hours worked per week", + "isCorrect": false + } + ], + "explanation": "Robinson's linear regression included country, years of programming experience, developer type and language (49 categories), formal education, open source contribution, hobby programming and company size, alongside tabs versus spaces. Editor and hours worked weren't in the model.\n\nControlling for a factor means comparing like with like: a spaces user and a tabs user in the same country, with the same experience, writing the same language. After those adjustments, spaces were still **associated with an 8.6% higher salary** (95% confidence interval roughly 6% to 10.4%). Robinson noted that this is about the same as 2.4 extra years of experience, and the gap showed up in every subgroup he examined.\n\nSo the obvious explanations, such as \"spaces users live in the US\" or \"spaces users write better-paid languages\", don't account for it. Factors nobody measured still could, and the next Question is about them.", + "references": "[Stack Overflow Blog: Developers who use spaces make more money than those who use tabs (David Robinson, 2017)](https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/), [GitHub: dgrtwo/tabs-spaces-post (the analysis code)](https://github.com/dgrtwo/tabs-spaces-post)" + }, + { + "id": "switch-to-spaces-for-a-raise", + "title": "A colleague reads the tabs-versus-spaces result and switches their editor to spaces, expecting a raise. What does the evidence support?", + "type": "single-choice", + "options": [ + { + "text": "Something that goes along with using spaces also goes along with higher pay, and the data can't say what that something is", + "isCorrect": true + }, + { + "text": "Using spaces causes higher pay, because the model controlled for the important factors", + "isCorrect": false + }, + { + "text": "Tabs users are worse programmers, which is why they earn less", + "isCorrect": false + }, + { + "text": "Nothing at all: 12,426 salaries is too small a sample to find any pattern", + "isCorrect": false + } + ], + "explanation": "The survey is **observational**: nobody was assigned to tabs or spaces. It can show that two things occur together. It can't show which one drives the other, or whether a third factor drives both. Robinson himself wrote that correlation isn't causation and that you can never be sure you've controlled for every confounding factor.\n\nA confounder here is anything linked to both indentation and pay that the model left out. A few guesses, not findings: working at a company that enforces a style guide or an automatic formatter, working on a team with more mature tooling, or the kind of project someone works on. Any of these could make spaces more likely *and* pay higher, without either one affecting the other.\n\nSample size isn't the problem. With over 12,000 salaries, the association is very unlikely to be chance. A large sample tells you the pattern is real, but not why it exists. To settle causation you'd have to assign developers to tabs or spaces at random and wait for their salaries, and nobody is going to run that experiment.", + "references": "[Stack Overflow Blog: Developers who use spaces make more money than those who use tabs (David Robinson, 2017)](https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/), [Wikipedia: Confounding](https://en.wikipedia.org/wiki/Confounding)" + }, + { + "id": "vim-quit-without-saving", + "title": "You're in Vim, you've made changes you don't want, and you want out. Type a command that quits without saving.", + "description": "Assume you've already pressed Esc. Type the command exactly as you'd type it into Vim, without pressing Enter.", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": [ + ":q!", + ":quit!", + ":qa!", + ":qall!", + ":quitall!", + "ZQ" + ], + "caseSensitive": true + }, + "explanation": "**`:q!`** does it. The colon switches Vim from Normal mode to Command-line mode, `q` means quit, and `!` means \"I know there are unsaved changes, throw them away\". Press Enter to run it. `:qa!` does the same for every open window. **`ZQ`**, typed in Normal mode with no colon, is a shortcut for `:q!`.\n\nPlain `:q` won't work here. With unsaved changes it refuses and shows *No write since last change (add ! to override)*. That's the safety net, and the `!` is how you tell Vim you mean to lose the changes.\n\nMost people get stuck one step earlier. Vim starts in **Normal mode**, where letters are commands, so typing `quit` runs a string of editing commands instead of quitting. If you're not sure which mode you're in, press Esc. It always brings you back to Normal mode.\n\nCase matters here. `ZQ` quits, but `zq` doesn't. Lowercase `z` starts a different set of commands, mostly for folding and scrolling.\n\nTry it now: run `vim` in a terminal, press `i`, type something, press Esc, then type `:q!` and press Enter.", + "references": "[Vim help: editing.txt, writing and quitting](https://vimhelp.org/editing.txt.html#write-quit), [Vim help: E37](https://vimhelp.org/message.txt.html#E37)", + "images": [ + { + "src": "vim-modes-state-machine.svg", + "alt": "State diagram of Vim's modes. Normal mode in the middle. Keys i, a or o go from Normal to Insert, and Esc goes back. A colon goes from Normal to Command-line, and Esc cancels back. From Normal, ZZ saves and closes and ZQ discards and closes. From Command-line, :wq then Enter saves and closes, and :q! then Enter discards and closes. A note says plain :q refuses when there are unsaved changes.", + "caption": "Diagram drawn for this Quiz from Vim's `:help` documentation.", + "placement": "explanation", + "width": 720, + "height": 470 + } + ] + }, + { + "id": "vim-save-and-quit", + "title": "This time you want to keep your edits. Type a Vim command that saves the file and quits.", + "description": "Again, assume you've pressed Esc, and leave out the final Enter.", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": [ + ":wq", + ":wq!", + ":x", + ":x!", + ":xit", + ":exit", + ":wqa", + ":wqall", + ":xa", + ":xall", + "ZZ" + ], + "caseSensitive": true + }, + "explanation": "**`:wq`** writes the file (`w`) and then quits (`q`). Vim's Command-line commands combine like that.\n\n`:x` and `ZZ` (Normal mode, no colon) are close shortcuts. They write the file only if it changed, then quit. `:wq` always writes, which updates the file's modification time even when nothing changed, and that can make build tools like `make` think the file needs rebuilding. `:x` leaves an unchanged file alone.\n\nIf the write fails, for example because the file is read-only or the buffer has no name yet, Vim stays open, so you can't lose work by accident. For an unnamed buffer, save with `:w filename`, then quit.\n\nAs with `ZQ`, case matters. `ZZ` saves and quits, but `zz` just scrolls the current line to the middle of the screen.", + "references": "[Vim help: editing.txt, writing and quitting](https://vimhelp.org/editing.txt.html#write-quit)" + }, + { + "id": "exit-vim-million-views", + "title": "The Stack Overflow question \"How to exit the Vim editor?\" was asked in 2012. When did Stack Overflow announce it had helped one million developers?", + "type": "single-choice", + "options": [ + { + "text": "2017", + "isCorrect": true + }, + { + "text": "2012, within months of being asked", + "isCorrect": false + }, + { + "text": "2014", + "isCorrect": false + }, + { + "text": "2021", + "isCorrect": false + } + ], + "explanation": "Stack Overflow published the milestone in **May 2017**, almost five years after the question was asked in August 2012.\n\nThe traffic built up slowly. Robinson's chart shows the question got little attention at first, grew for about two years as it climbed search rankings, and then held roughly steady. Evergreen questions often look like this. Search engines keep sending new people who hit the same problem, and the counter keeps ticking.\n\nFor scale, the question got about 0.005% of all Stack Overflow question traffic, one visit in every 20,000. That sounds tiny, but Stack Overflow gets a lot of traffic.", + "references": "[Stack Overflow Blog: Stack Overflow: Helping one million developers exit Vim (David Robinson, 2017)](https://stackoverflow.blog/2017/05/23/stack-overflow-helping-one-million-developers-exit-vim/), [Stack Overflow: How do I exit Vim?](https://stackoverflow.com/questions/11828270/how-do-i-exit-vim)" + }, + { + "id": "stuck-in-vim-per-hour", + "title": "Guess: at weekday peak hours in 2017, about how many people an hour visited Stack Overflow to find out how to exit Vim?", + "description": "Enter a whole number. Answers within 20 of the reported figure count.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 80 + ], + "tolerance": 20 + }, + "explanation": "Robinson reported **about 80 people an hour** during weekday peak hours, more than one a minute.\n\nThe number counts page visits. It doesn't count unique people, or only people who were really trapped. Some visitors were just curious, and some came back more than once. Visits to a help page are a *proxy* for people who got stuck, and a good analysis says which one it counted. Robinson's does.\n\nThe weekday peak is a clue too. If the traffic follows working hours, most of these people were probably stuck at work, in the middle of another task. The next Questions pick up from there.", + "references": "[Stack Overflow Blog: Stack Overflow: Helping one million developers exit Vim (David Robinson, 2017)](https://stackoverflow.blog/2017/05/23/stack-overflow-helping-one-million-developers-exit-vim/)" + }, + { + "id": "who-gets-stuck-most", + "title": "Among visitors to Vim questions on Stack Overflow, developers from which group were the most likely to be looking for the exit?", + "description": "Robinson grouped visitors by the tags they visited most and compared how often each group's Vim traffic went to the exit question.", + "type": "single-choice", + "options": [ + { + "text": "Front-end web developers, such as those who visit jQuery, CSS and AngularJS questions", + "isCorrect": true + }, + { + "text": "Microsoft developers, such as those who visit C# and SQL Server questions", + "isCorrect": false + }, + { + "text": "C and C++ developers", + "isCorrect": false + }, + { + "text": "Python and Ruby developers", + "isCorrect": false + } + ], + "explanation": "**Front-end web developers** were the most likely. Microsoft developers (C#, SQL Server) and mobile developers (Android, iOS) came next. C, C++, Python and Ruby developers were the *least* likely.\n\nThe metric is the share of each group's Vim-question visits that went to the exit question. Because it's a ratio, group size drops out, and a huge group with no Vim trouble still scores low. A raw count of visits would mostly show which technologies have the most developers.\n\nThe same analysis found differences by country. Ukraine, Turkey and Indonesia scored high, and China, Korea and Japan scored low. The post read the low scores as people who open Vim on purpose and already know how to leave.", + "references": "[Stack Overflow Blog: Stack Overflow: Helping one million developers exit Vim (David Robinson, 2017)](https://stackoverflow.blog/2017/05/23/stack-overflow-helping-one-million-developers-exit-vim/)" + }, + { + "id": "why-front-end-developers-get-stuck", + "title": "What is the most plausible reason front-end developers got stuck in Vim more often than C or Python developers?", + "type": "single-choice", + "options": [ + { + "text": "They mostly work in IDEs, so they meet Vim by accident, for example when a Git command opens it", + "isCorrect": true + }, + { + "text": "Front-end work requires editing files in Vim more often", + "isCorrect": false + }, + { + "text": "Vim can't open JavaScript or CSS files correctly", + "isCorrect": false + }, + { + "text": "Front-end developers make more typos, which trigger Vim's modes", + "isCorrect": false + } + ], + "explanation": "Robinson's explanation is that front-end developers usually work in an IDE or a graphical editor, while C, C++, Python and Ruby developers more often use plain text editors, Vim included. If you chose Vim, you know how to leave it. If another tool *dropped you into* Vim, you might not.\n\nThis is an interpretation. The traffic logs show *which groups* visit the exit question. The \"IDE users arrive by accident\" story fits that pattern, and the country pattern too, but the logs didn't measure it directly.\n\nThe other options don't hold up. Vim opens any text file, JavaScript and CSS included, and front-end work doesn't require Vim. What differs is how often people end up in it without choosing to.", + "references": "[Stack Overflow Blog: Stack Overflow: Helping one million developers exit Vim (David Robinson, 2017)](https://stackoverflow.blog/2017/05/23/stack-overflow-helping-one-million-developers-exit-vim/)" + }, + { + "id": "how-git-drops-you-into-vim", + "title": "You run `git commit` without `-m` on a machine where you've never configured an editor, and a Vim screen appears. Why?", + "type": "single-choice", + "options": [ + { + "text": "Git needs an editor for the commit message and, when none is configured, falls back to a compile-time default that is usually `vi`", + "isCorrect": true + }, + { + "text": "Git ships its own built-in copy of Vim and always uses it for commit messages", + "isCorrect": false + }, + { + "text": "Your terminal opens Vim whenever a program asks for multi-line input", + "isCorrect": false + }, + { + "text": "Git detected an error in your commit and opened Vim so you could fix it", + "isCorrect": false + } + ], + "explanation": "Without `-m`, Git opens an editor for the commit message. To pick one, it checks these in order: the `GIT_EDITOR` environment variable, the `core.editor` config setting, `VISUAL`, `EDITOR`, and finally the default set when Git was compiled, **which is usually `vi`**. On many systems `vi` launches Vim.\n\nSo nothing went wrong. Every setting was empty, and Git used its last fallback. The same happens when `git merge` asks for a merge message, and with `git rebase -i`.\n\nIf you're already in Vim, type your message, press Esc, then `:wq`. To get out without committing, leave the message empty and save. Git aborts a commit with an empty message.\n\nTo avoid this next time, set an editor you know. For VS Code:\n\n```bash\ngit config --global core.editor \"code --wait\"\n```\n\nKeep the `--wait`. Without it, the `code` command returns right away, and Git sees an empty message.", + "references": "[Git documentation: git-var, GIT_EDITOR](https://git-scm.com/docs/git-var), [Git documentation: git-commit](https://git-scm.com/docs/git-commit), [VS Code docs: Source control in VS Code](https://code.visualstudio.com/docs/sourcecontrol/overview)", + "images": [ + { + "src": "git-editor-fallback-chain.svg", + "alt": "A list of the five places Git checks to choose an editor: 1 GIT_EDITOR environment variable, 2 core.editor git config setting, 3 VISUAL environment variable, 4 EDITOR environment variable, each marked 'not set? go down'. Step 5, highlighted, is vi, Git's built-in default. A note says that if steps 1 to 4 are empty you land in vi, which on many systems is Vim, and that this happens with git commit without -m, git merge and git rebase -i.", + "caption": "Order of preference from the Git documentation for `git var GIT_EDITOR`. Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 440 + } + ] + }, + { + "id": "why-vim-has-modes", + "title": "Why does vi, and Vim after it, use modes where single letters are commands, instead of letting you type text right away?", + "type": "single-choice", + "options": [ + { + "text": "vi was built in the 1970s for slow terminals, where short commands let you work faster than the screen could redraw", + "isCorrect": true + }, + { + "text": "Terminals at the time had no Ctrl or Alt keys, so letters were the only way to send commands", + "isCorrect": false + }, + { + "text": "Modes saved disk space by storing commands as single bytes", + "isCorrect": false + }, + { + "text": "Bill Joy copied the design from Emacs", + "isCorrect": false + } + ], + "explanation": "vi comes from a line of Unix editors: Ken Thompson's **ed** (1969), George Coulouris's **em**, and Bill Joy's **ex**. vi is the visual mode of ex. ed was famously terse and reported most errors with a single `?`, because it ran on teletypes over slow connections.\n\nJoy wrote vi over a **300 baud modem**, about 30 characters a second. He said the single-character commands, and the ability to type ahead of the display, came from that limit. The screen redrew slower than he could think, and he wanted to keep working anyway. When one keystroke means \"delete a word\", you send one character instead of a long command.\n\nHis terminal, a Lear Siegler ADM-3A, explains two more quirks. Its Escape key sat where Tab is on today's keyboards, so switching modes was an easy reach. And the `h`, `j`, `k` and `l` keys had arrows printed on them, because the terminal had no separate arrow keys. That's why they still move the cursor in Vim.\n\nThose terminals did have Ctrl keys. Emacs, built around Ctrl-key chords, took the opposite approach, and vi didn't borrow from it.", + "references": "[Wikipedia: vi (text editor)](https://en.wikipedia.org/wiki/Vi_(text_editor)), [Wikipedia: ed (text editor)](https://en.wikipedia.org/wiki/Ed_(text_editor)), [The Register: Bill Joy's greatest gift to man, the vi editor (2003)](https://www.theregister.com/2003/09/11/bill_joys_greatest_gift/)" + }, + { + "id": "editor-of-the-beast", + "title": "Richard Stallman's parody Church of Emacs calls vi \"the editor of the beast\". Where does the name come from?", + "type": "single-choice", + "options": [ + { + "text": "\"vi vi vi\" read as Roman numerals is 6 6 6, the number of the beast", + "isCorrect": true + }, + { + "text": "vi was the sixth editor released for Unix, in version 6", + "isCorrect": false + }, + { + "text": "vi's source code was 666 lines long", + "isCorrect": false + }, + { + "text": "vi's first release was on 6 June 1966", + "isCorrect": false + } + ], + "explanation": "In Roman numerals VI is 6, so **vi-vi-vi reads as 6-6-6**, the number of the beast. The joke comes from the Church of Emacs, a parody religion Stallman created. In it he plays **Saint IGNUcius**, in a robe and a halo made from an old disk platter.\n\nThe church preaches software freedom more than it attacks vi. Using a free version of vi counts as a penance, and the real sin is proprietary software.\n\nThe editor war is one of Usenet's original holy wars, with recorded flame wars going back to at least 1985. vi users answered with their own jokes about Emacs and its long Ctrl and Meta key chords. Nobody ever settled it with data, which is part of why it could run for decades.", + "references": "[Wikipedia: Editor war](https://en.wikipedia.org/wiki/Editor_war), [Stallman.org: Saint IGNUcius](https://stallman.org/saint.html)" + }, + { + "id": "editor-share-2025-survey", + "title": "In the 2025 Stack Overflow Developer Survey, 75.9% of respondents said they regularly used VS Code, 24.3% Vim and 14% Neovim. Emacs appeared only as a write-in answer, at 0.15%. Which conclusions does this data support?", + "description": "The question was \"Which development environments and AI-enabled code editing tools did you use regularly over the past year?\", with \"check all that apply\". About 26,000 people answered it.", + "type": "multiple-choice", + "options": [ + { + "text": "VS Code was the most used development environment among these respondents", + "isCorrect": true + }, + { + "text": "Many respondents used more than one editor", + "isCorrect": true + }, + { + "text": "About one respondent in four said they used Vim regularly", + "isCorrect": true + }, + { + "text": "Emacs is used by fewer than 1 developer in 500", + "isCorrect": false + }, + { + "text": "Vim's share has shrunk since 2017, because VS Code took its users", + "isCorrect": false + }, + { + "text": "These percentages describe all professional developers worldwide", + "isCorrect": false + } + ], + "explanation": "Three conclusions hold. VS Code led clearly, for the fourth year running. About a quarter of respondents checked Vim. And because people could check every tool they used, the percentages add up to far more than 100%. VS Code alone is 75.9%, and Visual Studio, Notepad++, IntelliJ IDEA and Vim each got another 24% to 29%. Plenty of people use VS Code *and* Vim, for example Vim for quick edits over SSH.\n\nThe other three don't follow:\n\n- **Emacs at 0.15%** comes from how the form was built. Emacs wasn't a listed option, so the only way to count it was to type it in, and most people don't bother. Write-in answers are always undercounted, so this number says almost nothing about how many people use Emacs.\n- **A trend** needs data from several years, collected the same way. One year's snapshot can't show that Vim lost users, let alone *why*.\n- **All developers** is too broad. These respondents chose to take a survey promoted by Stack Overflow. That's a large sample, but not a random one.", + "references": "[Stack Overflow Developer Survey 2025: Technology, Dev IDEs](https://survey.stackoverflow.co/2025/technology), [Stack Overflow Developer Survey 2025: Methodology](https://survey.stackoverflow.co/2025/methodology)" + }, + { + "id": "guess-the-year-vi", + "title": "Guess the year: when did Bill Joy build the visual mode of the ex editor that became vi?", + "description": "Enter a year. Answers within one year of it count.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 1977 + ], + "tolerance": 1 + }, + "explanation": "Joy, then a graduate student at UC Berkeley, added a full-screen visual mode to ex in **1976 and 1977**. Sources disagree on the exact date. Wikipedia's summary says 1976, while the ex source history points to a redesign from June to October 1977. That's why any answer from 1976 to 1978 counts.\n\nThe release history has layers too, which is common. \"When was X invented?\" often has more than one honest answer:\n\n- **March 1978**: ex 1.1 shipped in the first Berkeley Software Distribution (1BSD).\n- **May 1979**: ex 2.0 in 2BSD installed the program under the name `vi`, starting straight in visual mode. vi and ex are the same program.\n- **1991**: Bram Moolenaar finished Vim 1.14, its first public release, which went out on an Amiga Fish Disk in January 1992.\n\nSo Vim's modes are nearly 50 years old, and the question of how to leave them is older than most of the developers asking it.", + "references": "[Wikipedia: vi (text editor)](https://en.wikipedia.org/wiki/Vi_(text_editor)), [Wikipedia: Vim (text editor)](https://en.wikipedia.org/wiki/Vim_(text_editor))" + }, + { + "id": "holy-wars-what-the-data-shows", + "title": "Looking back across the Quiz, which of these claims does the evidence support?", + "type": "multiple-choice", + "options": [ + { + "text": "In 2017 survey data, spaces users reported higher salaries than tabs users, even after controlling for country, experience and language", + "isCorrect": true + }, + { + "text": "Among visitors to Vim questions, front-end developers were more likely than C developers to be looking for the exit", + "isCorrect": true + }, + { + "text": "With no editor configured, Git usually falls back to `vi`", + "isCorrect": true + }, + { + "text": "Switching from tabs to spaces will raise your salary", + "isCorrect": false + }, + { + "text": "Front-end developers are less skilled programmers than C developers", + "isCorrect": false + }, + { + "text": "Most developers use Vim as their main editor", + "isCorrect": false + } + ], + "explanation": "Each supported claim stays inside what was measured:\n\n- **The salary gap** is a real association in self-reported survey data, and it held up after controls for the obvious confounders. It's still only an association.\n- **The Vim ranking** compares the share of each group's Vim traffic that went to the exit question. It measures confusion about one editor, not programming skill. The \"IDE users land in Vim by accident\" story is a plausible interpretation on top of that.\n- **Git's fallback** isn't data. It's documented design, and you can check it yourself with `git var GIT_EDITOR`.\n\nEach unsupported claim goes one step too far. \"Switch and earn more\" turns a correlation into a cause. \"Less skilled\" swaps what was measured, finding the exit, for something that wasn't measured at all, ability. And \"most developers use Vim\" misreads 24.3% of one self-selected survey as a majority.\n\nNext time a holy war starts, ask what was measured, on whom, and whether something else could explain it.", + "references": "[Stack Overflow Blog: Developers who use spaces make more money than those who use tabs (David Robinson, 2017)](https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/), [Stack Overflow Blog: Stack Overflow: Helping one million developers exit Vim (David Robinson, 2017)](https://stackoverflow.blog/2017/05/23/stack-overflow-helping-one-million-developers-exit-vim/), [Git documentation: git-var, GIT_EDITOR](https://git-scm.com/docs/git-var), [Stack Overflow Developer Survey 2025: Technology, Dev IDEs](https://survey.stackoverflow.co/2025/technology)" + } + ] +} diff --git a/content/quizzes/developer-holy-wars-measured/git-editor-fallback-chain.svg b/content/quizzes/developer-holy-wars-measured/git-editor-fallback-chain.svg new file mode 100644 index 0000000..4860fb4 --- /dev/null +++ b/content/quizzes/developer-holy-wars-measured/git-editor-fallback-chain.svg @@ -0,0 +1,79 @@ + + + + + Which editor does git commit open? + Git checks these in order and uses the first one that's set + + 1 + GIT_EDITOR + environment variable + not set? ↓ + + 2 + core.editor + git config setting + not set? ↓ + + 3 + VISUAL + environment variable + not set? ↓ + + 4 + EDITOR + environment variable + not set? ↓ + + 5 + vi + Git's built-in default, set when Git was compiled + If steps 1 to 4 are all empty, you land in vi, which on many systems is Vim. + It happens with git commit without -m, git merge and git rebase -i. + diff --git a/content/quizzes/developer-holy-wars-measured/tabs-spaces-both-split.svg b/content/quizzes/developer-holy-wars-measured/tabs-spaces-both-split.svg new file mode 100644 index 0000000..cc7e16a --- /dev/null +++ b/content/quizzes/developer-holy-wars-measured/tabs-spaces-both-split.svg @@ -0,0 +1,67 @@ + + + + + Stack Overflow 2017 survey: tabs or spaces? + 28,657 professional developers who answered the indentation question + Spaces + + 41.8% + Tabs + + 40.7% + Both + + 17.5% + The same split as one bar + + + + Tabs and spaces are nearly tied, and about one developer in six uses both. + diff --git a/content/quizzes/developer-holy-wars-measured/vim-modes-state-machine.svg b/content/quizzes/developer-holy-wars-measured/vim-modes-state-machine.svg new file mode 100644 index 0000000..821dc5b --- /dev/null +++ b/content/quizzes/developer-holy-wars-measured/vim-modes-state-machine.svg @@ -0,0 +1,88 @@ + + + + + + + + + + Vim's modes: the keys that get you out + Lost? Press Esc to get back to Normal mode. Pressing it twice does no harm. + + Insert + keys type text + + Normal + keys are commands + + Command-line + type a : command + + Vim closes + back to the shell + + i a o + + Esc + + : + + Esc + (cancels) + + ZZ save + ZQ discard + + :wq ↵ save + :q! ↵ discard + Vim starts in Normal mode, where letters run commands. Typing "quit" + there runs editing commands instead of quitting. + Plain :q ↵ won't quit with unsaved changes. Vim shows "No write since last change (add ! to override)". + diff --git a/content/quizzes/game-bugs-that-became-features.json b/content/quizzes/game-bugs-that-became-features.json new file mode 100644 index 0000000..eb9058d --- /dev/null +++ b/content/quizzes/game-bugs-that-became-features.json @@ -0,0 +1,441 @@ +{ + "schemaVersion": 1, + "id": "game-bugs-that-became-features", + "title": "Game bugs that became features: Space Invaders, the Creeper and the Konami code", + "description": "A slow processor gave *Space Invaders* one of the first difficulty curves. Minecraft's best-known monster is a broken pig. Fighting-game combos started as an accident, and the most famous cheat code shipped because someone forgot to delete it. Each Question tells you what the developers saw and asks what they did next. One famous bug in this Quiz never happened.", + "language": "en", + "tags": [ + "games", + "debugging", + "internet-culture" + ], + "author": "Quizbun", + "questions": [ + { + "id": "space-invaders-speed-up", + "title": "In *Space Invaders*, the aliens march faster as you shoot them down. Why?", + "type": "single-choice", + "options": [ + { + "text": "The hardware drew each frame faster when fewer aliens were left, and the designer kept the effect", + "isCorrect": true + }, + { + "text": "A timer raised the aliens' speed every few seconds", + "isCorrect": false + }, + { + "text": "Each alien you destroyed added a fixed amount to the formation's speed", + "isCorrect": false + }, + { + "text": "The cabinet's processor ran faster as it warmed up", + "isCorrect": false + } + ], + "explanation": "Nobody programmed the speed-up. The game moves the aliens a little after each frame it draws. With a full formation, the Intel 8080 processor takes a long time to draw a frame. Shoot some aliens and there's less to draw, so each frame finishes sooner. The step per frame stays the same, but more frames fit into each second, so the formation speeds up.\n\nThe last alien is the fastest because the processor has almost nothing else to draw. In the image, frame time shrinks as aliens disappear, and steps per second grow.\n\nTo build this on purpose, you'd use a timer. *Space Invaders* got the same result without any speed code.", + "references": "[Wikipedia: Space Invaders](https://en.wikipedia.org/wiki/Space_Invaders)", + "images": [ + { + "src": "space-invaders-frame-time.svg", + "alt": "Two bar charts side by side for 55, 30, 10 and 1 aliens left. Bars for the time to draw one frame get shorter as aliens disappear, from longest at 55 to shortest at 1. Bars for formation steps per second do the opposite, from shortest at 55 to longest at 1. A note says the step size never changes, only the number of steps per second does.", + "caption": "Illustrative proportions drawn for this Quiz, not measured timings.", + "placement": "explanation", + "width": 720, + "height": 450 + } + ] + }, + { + "id": "space-invaders-steady-speed-fix", + "title": "Suppose Tomohiro Nishikado had wanted the aliens to keep a steady speed. What is the usual fix for this kind of bug?", + "type": "single-choice", + "options": [ + { + "text": "Tie movement to elapsed time, not frames: move by speed × time since the last update", + "isCorrect": true + }, + { + "text": "Make the alien sprites smaller so every frame draws faster", + "isCorrect": false + }, + { + "text": "Make each step bigger as more aliens are destroyed", + "isCorrect": false + }, + { + "text": "Draw the aliens in a random order each frame", + "isCorrect": false + } + ], + "explanation": "The bug is that movement is counted in frames, so when frame times change, speed changes too. The fix is to count time instead. Check how long has passed since the last update and move by speed × elapsed time. Game developers call this elapsed time the *delta time*. A related fix runs the game logic on a fixed timer, however long drawing takes.\n\nSmaller sprites would make every frame faster, but a frame with 55 aliens would still take longer than a frame with 1, so the speed-up would stay. Bigger steps would make it worse.\n\nPC games in the 1980s made the same mistake. Some tied their speed to the processor and ran too fast on newer PCs. That's one reason PCs had a *turbo* button that could slow the machine back down to the original speed.", + "references": "[Wikipedia: Turbo button](https://en.wikipedia.org/wiki/Turbo_button), [Wikipedia: Space Invaders](https://en.wikipedia.org/wiki/Space_Invaders)" + }, + { + "id": "space-invaders-release-year", + "title": "In what year was *Space Invaders* released in Japan?", + "description": "Answer with a four-digit year.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 1978 + ], + "tolerance": 1 + }, + "explanation": "**1978**. Taito released it in Japan in July 1978.\n\nNishikado built much of the hardware and development tools himself, adding his own circuits to the arcade board he started from. Even so, the 8080 couldn't draw a full formation quickly, and that limit caused the speed-up from the first Question.", + "references": "[Wikipedia: Space Invaders](https://en.wikipedia.org/wiki/Space_Invaders)" + }, + { + "id": "why-nishikado-kept-it", + "title": "Nishikado noticed the aliens speeding up during development. Why did he keep it?", + "type": "single-choice", + "options": [ + { + "text": "He felt it made the game more interesting, and it made up for the hardware's limits", + "isCorrect": true + }, + { + "text": "He ran out of time before the release date to fix it", + "isCorrect": false + }, + { + "text": "Fixing it would have needed a more expensive processor", + "isCorrect": false + }, + { + "text": "He didn't notice it until players reported it", + "isCorrect": false + } + ], + "explanation": "Nishikado could have written code to cancel out the speed-up. He kept it as part of the gameplay instead. Looking back, he said it made the game more interesting and made up for what the hardware couldn't do.\n\nIt also matched something he already wanted. Many arcade games of the time simply ended when a timer ran out, but Nishikado wanted his to get harder the longer you played. The accident gave him that. Each wave starts slowly and gets tense as it thins out, and the last alien is the hardest to hit.\n\nThis makes *Space Invaders* one of the earliest games with a difficulty curve inside each wave. It's also the first example of a pattern in this Quiz: a bug stays in when the team decides the result is fun.", + "references": "[Wikipedia: Space Invaders](https://en.wikipedia.org/wiki/Space_Invaders)" + }, + { + "id": "creeper-pig-model", + "title": "In August 2009, Minecraft creator Markus Persson was making a pig. It loaded looking like the box labeled \"As it loaded\". What went wrong, and what did he do?", + "type": "single-choice", + "options": [ + { + "text": "He had swapped the length and the height, and he kept the result as a new monster, the Creeper", + "isCorrect": true + }, + { + "text": "He deleted the model and started the pig again", + "isCorrect": false + }, + { + "text": "The model loaded upside down, and he fixed the rotation", + "isCorrect": false + }, + { + "text": "The game scaled it by mistake, and it became a chicken", + "isCorrect": false + } + ], + "explanation": "Persson mixed up the model's dimensions, so the length became the height. A long, low body turned into a short, tall one. Instead of deleting it, he said he'd keep it \"for a creepy creature\". That tall box became the Creeper's body, and the Creeper became Minecraft's best-known monster.\n\nSwapped arguments are an easy bug to write. When a function takes several numbers of the same type, such as `width, height, depth`, nothing stops you from passing them in the wrong order. With named parameters, or an object with named fields, the compiler or a reader can catch the mistake.", + "references": "[Wikipedia: Creeper (Minecraft)](https://en.wikipedia.org/wiki/Creeper_(Minecraft))", + "images": [ + { + "src": "pig-model-as-planned-and-as-loaded.svg", + "alt": "Two plain boxes with dimension arrows. On the left, labeled As planned, a long, low box: the length arrow is long and the height arrow is short. On the right, labeled As it loaded, a short, tall box: its length matches the planned height, and its height matches the planned length.", + "caption": "Plain shapes drawn for this Quiz. No game art.", + "width": 720, + "height": 470 + } + ] + }, + { + "id": "creeper-green-skin", + "title": "Where did the Creeper's mottled green skin come from?", + "type": "single-choice", + "options": [ + { + "text": "Persson based it on the game's existing leaves texture", + "isCorrect": true + }, + { + "text": "An artist painted it to look like a cactus", + "isCorrect": false + }, + { + "text": "It's a placeholder color for models without a texture", + "isCorrect": false + }, + { + "text": "It was the pig's texture with the colors inverted", + "isCorrect": false + } + ], + "explanation": "He reused something the game already had. After deciding to keep the broken pig, Persson gave it a green texture based on Minecraft's leaves.\n\nReusing textures, sounds and models is normal in games, especially for a solo developer on a pre-alpha project. That was Minecraft in 2009, and the Creeper is a good example: a coding mistake with a borrowed texture.", + "references": "[Wikipedia: Creeper (Minecraft)](https://en.wikipedia.org/wiki/Creeper_(Minecraft))" + }, + { + "id": "creeper-first-weeks", + "title": "Which of these are true about the Creeper's first weeks?", + "type": "multiple-choice", + "options": [ + { + "text": "The misshapen model first appeared on 20 August 2009", + "isCorrect": true + }, + { + "text": "Persson gave it an aggressive AI and made it a hostile monster", + "isCorrect": true + }, + { + "text": "It entered the game on 1 September 2009, in a pre-alpha build", + "isCorrect": true + }, + { + "text": "A hired artist had sketched it first, and the bug only changed its size", + "isCorrect": false + }, + { + "text": "Players found the broken pig in a public build and voted to keep it", + "isCorrect": false + } + ], + "explanation": "The whole story took under two weeks. The model appeared on 20 August 2009 while Persson was building the pig. He gave it an aggressive AI, made it a hostile mob, and added it to the game on 1 September 2009, in a pre-alpha version called `0.24_SURVIVAL_TEST`.\n\nThere was no concept sketch. The shape came straight from the bug, and the texture came from the leaves. Players didn't vote on it either. Persson decided alone, and the public first met the Creeper as a finished monster.", + "references": "[Wikipedia: Creeper (Minecraft)](https://en.wikipedia.org/wiki/Creeper_(Minecraft))" + }, + { + "id": "street-fighter-ii-combo-discovery", + "title": "*Street Fighter II* (1991) made combos famous. How did its producer, Noritaka Funamizu, first notice them?", + "type": "single-choice", + "options": [ + { + "text": "During a bug check on the car-smashing bonus stage, he saw that extra hits were possible", + "isCorrect": true + }, + { + "text": "Arcade players found them months after release and sent in letters", + "isCorrect": false + }, + { + "text": "A tester wrote a list of button sequences that couldn't be blocked", + "isCorrect": false + }, + { + "text": "He copied the idea from an earlier fighting game", + "isCorrect": false + } + ], + "explanation": "In the bonus stage, you beat up a parked car. While checking it for bugs, Funamizu noticed he could land extra hits, because a second attack could start before the first had finished. Chain these hits together and you get a *combo*: once the first hit lands, the opponent can't escape the rest.\n\nFunamizu thought the timing was too hard for players to use, so the team left it in as a hidden feature. Players proved him wrong. Combos became central to competitive fighting games, and later games designed them on purpose.\n\n*Street Fighter II* wasn't the first game with combos. Culture Brain's *Shanghai Kid* (1985) already had a combo system. *Street Fighter II* is the game that made them standard.", + "references": "[Wikipedia: Combo (video games)](https://en.wikipedia.org/wiki/Combo_(video_games))" + }, + { + "id": "street-fighter-ii-second-account", + "title": "In 2013, *Street Fighter II*'s lead designer, Akira Nishitani, explained canceling differently. What did he say it came from?", + "description": "*Canceling* means cutting a normal attack short to start a special move straight away. It's what makes many *Street Fighter II* combos possible.", + "type": "single-choice", + "options": [ + { + "text": "A system meant to make special moves easier to input, which also let them start during a normal attack", + "isCorrect": true + }, + { + "text": "A deliberate feature planned from the first design documents", + "isCorrect": false + }, + { + "text": "A memory error that skipped the end of each attack animation", + "isCorrect": false + }, + { + "text": "An arcade-board timing bug that Capcom couldn't fix in time", + "isCorrect": false + } + ], + "explanation": "Nishitani said the team made special-move input more forgiving. If a player pressed the button too fast, the move wouldn't come out, so the game accepted inputs more generously. As a side effect, a special move could start after a normal attack had hit.\n\nHe didn't call it a bug. The team hadn't expected it, but decided during development to keep it. It \"didn't seem to cause any bugs\", he said, so it could \"be a feature to expand the gameplay\".\n\nSo there are two accounts. Funamizu remembers spotting extra hits during a bug check, and Nishitani describes an unplanned side effect of the input system. Both can be true, since two people in different roles saw the same behavior from different sides. Histories built from interviews often look like this. When accounts differ, report both and say who said what.", + "references": "[Game Developer: Street Fighter II designer opens up about the canceling bug (2013)](https://www.gamedeveloper.com/business/-i-street-fighter-ii-i-designer-opens-up-about-the-cancelling-bug-), [Wikipedia: Combo (video games)](https://en.wikipedia.org/wiki/Combo_(video_games))" + }, + { + "id": "konami-code-origin", + "title": "Why did Kazuhisa Hashimoto create the Konami code?", + "description": "The sequence is ↑ ↑ ↓ ↓ ← → ← → B A on an NES controller. It first appeared in the NES version of *Gradius* (1986).", + "type": "single-choice", + "options": [ + { + "text": "He found *Gradius* too hard to play through while testing it, so he gave himself every power-up", + "isCorrect": true + }, + { + "text": "Konami's marketing team wanted a secret to print in game magazines", + "isCorrect": false + }, + { + "text": "It was a copy-protection check that proved the cartridge was genuine", + "isCorrect": false + }, + { + "text": "He wanted a way to demo the final levels at trade shows", + "isCorrect": false + } + ], + "explanation": "It was a testing tool. Hashimoto was porting the 1985 arcade game *Gradius* to the NES and had to play it over and over to check his work. The game was too hard for him to get through, so he added a code that gave his ship every power-up.\n\nDevelopers still do this. With a debug menu, a skip-level key or a god mode, a tester can reach the part they need to check without being good at the game. Trouble starts when the shortcut reaches players, as the next Question shows.", + "references": "[Wikipedia: Konami Code](https://en.wikipedia.org/wiki/Konami_Code)" + }, + { + "id": "konami-code-why-it-shipped", + "title": "The code was meant to be removed before *Gradius* shipped. Why was it still in the game?", + "type": "single-choice", + "options": [ + { + "text": "It was overlooked until mass production, and removing it that late risked new bugs", + "isCorrect": true + }, + { + "text": "Nintendo's rules required every game to include a cheat", + "isCorrect": false + }, + { + "text": "Hashimoto kept it on purpose as a hidden signature", + "isCorrect": false + }, + { + "text": "It was stored in a part of the chip that couldn't be changed", + "isCorrect": false + } + ], + "explanation": "Somebody forgot, and by the time anyone noticed, it was too late to change safely. Nobody spotted the code until the game was being prepared for mass production. The developers left it in, because removing it could have caused new bugs and glitches.\n\nThat's a real engineering trade-off. A change made just before release gets the least testing. Deleting a few lines of debug code sounds harmless, but you can't patch a cartridge, so any mistake would ship in every copy. A known, harmless cheat was the safer choice.\n\nKonami later put the code in many games on purpose, and it became the company's signature.", + "references": "[Wikipedia: Konami Code](https://en.wikipedia.org/wiki/Konami_Code)" + }, + { + "id": "contra-thirty-lives", + "title": "The code became famous through another NES game, *Contra*. How many lives did it give you there?", + "description": "Answer with a number.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 30 + ] + }, + "explanation": "**30 lives**. *Contra* is hard, and with the code many more players could finish it.\n\nThat's why so many players learned the code from *Contra* rather than *Gradius*, and why it's also called the \"Contra code\" or the \"30 lives code\".", + "references": "[Wikipedia: Konami Code](https://en.wikipedia.org/wiki/Konami_Code)" + }, + { + "id": "debug-shortcuts-lessons", + "title": "Which lessons from the Konami code apply to debug shortcuts in your own software?", + "type": "multiple-choice", + "options": [ + { + "text": "Keep debug-only code behind a build flag, so release builds leave it out automatically", + "isCorrect": true + }, + { + "text": "Deleting code just before a release is a change too, and it can introduce bugs", + "isCorrect": true + }, + { + "text": "If a shortcut is undocumented, users won't find it", + "isCorrect": false + }, + { + "text": "A test-only feature is safe to ship as long as it does nothing harmful in testing", + "isCorrect": false + } + ], + "explanation": "The Konami code shipped because removing it depended on someone remembering. A build flag takes memory out of it. Debug code is compiled only into development builds, so a release build can't contain it even if everyone forgets. Many languages and build tools support this, such as `#ifdef DEBUG` in C or build-time constants that a bundler strips.\n\nThe second lesson is why Konami left the code in. Removing code is a change, and a late change gets little testing.\n\nThe other two statements are traps. Players found the Konami code, and users find hidden routes, debug endpoints and test accounts too. \"Harmless in testing\" also misses the point. A cheat is harmless in a game, but a debug route that skips login is a security hole in a web app.", + "references": "[Wikipedia: Konami Code](https://en.wikipedia.org/wiki/Konami_Code)" + }, + { + "id": "tribes-skiing", + "title": "In *Starsiege: Tribes* (1998), players found they could build up huge speed by tapping jump at the right moment while running downhill. What did the developer, Dynamix, do about it?", + "type": "single-choice", + "options": [ + { + "text": "Turned it into an official feature of the sequel, *Tribes 2*", + "isCorrect": true + }, + { + "text": "Patched it out within a few months", + "isCorrect": false + }, + { + "text": "Left it alone and never acknowledged it", + "isCorrect": false + }, + { + "text": "Banned it from official tournaments", + "isCorrect": false + } + ], + "explanation": "The trick, called *skiing*, came from a quirk in the game's physics engine. With the right timing on a downhill slope, a player kept building momentum instead of losing it. Add the game's jetpacks, and skilled players could cross huge maps at high speed.\n\nDynamix built on it. In *Tribes 2* (2001), skiing became a deliberate game feature.\n\nThis story is different from the earlier ones. Nishikado and Persson decided to keep their bugs before anyone else saw them. *Tribes* players found skiing after release and built a play style around it, so removing it would have broken the game they played. Once users depend on a behavior, it's part of your product, whether you planned it or not.", + "references": "[Wikipedia: Starsiege: Tribes](https://en.wikipedia.org/wiki/Starsiege:_Tribes)" + }, + { + "id": "nuclear-gandhi-bug-that-never-was", + "title": "In *Civilization V* (2010), Gandhi is famously eager to use nuclear weapons. Which account of this \"bug that became a feature\" is accurate?", + "type": "single-choice", + "options": [ + { + "text": "There was no bug: *Civ V*'s designer set Gandhi's nuclear rating to the maximum as a joke, and the bug story appeared online in 2012", + "isCorrect": true + }, + { + "text": "An underflow in the 1991 *Civilization* made Gandhi aggressive, and *Civ V* kept it as a tribute", + "isCorrect": false + }, + { + "text": "*Civ V* shipped with an underflow bug, and a patch turned it into a feature", + "isCorrect": false + }, + { + "text": "The 1991 game's manual listed Gandhi's aggression as a known bug", + "isCorrect": false + } + ], + "explanation": "This is the Quiz's trap. The legend says the 1991 *Civilization* stored Gandhi's aggression as 1, democracy lowered it by 2, and the value wrapped around to 255, the maximum. The image shows the wraparound the story depends on.\n\nThe designers say it never happened. Sid Meier, who created the series, pointed out in his 2020 memoir that the game's integer variables were signed, so 1 − 2 would be −1, not 255. Brian Reynolds, lead designer of *Civilization II*, said the original game had only three aggression levels, so there was no 255 to wrap around to.\n\nThe nuke-happy Gandhi is real, but it was on purpose. In *Civilization V*, designer Jon Shafer gave Gandhi the maximum nuclear rating as a joke. Two years later, a TV Tropes user added the bug story without a source, and it spread from there.\n\nThe timeline settles it. The feature came first, and the \"bug\" was invented later to explain it. The Quiz *Tech myths that won't die* follows this myth further.", + "references": "[Wikipedia: Nuclear Gandhi](https://en.wikipedia.org/wiki/Nuclear_Gandhi)", + "images": [ + { + "src": "unsigned-8-bit-underflow-wheel.svg", + "alt": "A ring of the 256 values of an unsigned 8-bit number, from 0 to 255. An arrow goes two steps back from 1, past 0, to 255. A side panel reads: unsigned 8-bit, 1 − 1 = 0, 0 − 1 wraps to 255, so 1 − 2 = 255. A second panel reads: signed 8-bit, the same bit pattern means −1, so 1 − 2 = −1, as expected.", + "caption": "Diagram drawn for Quizbun, shared with the Quiz *Tech myths that won't die*.", + "placement": "explanation", + "width": 720, + "height": 440 + } + ] + }, + { + "id": "fix-it-or-ship-it", + "title": "Fix it or ship it? Which of these glitches did the developers keep, or build into a later game?", + "type": "multiple-choice", + "options": [ + { + "text": "*Space Invaders*' aliens speeding up as their numbers fall", + "isCorrect": true + }, + { + "text": "Minecraft's pig with its length and height swapped", + "isCorrect": true + }, + { + "text": "Canceling a normal attack into a special move in *Street Fighter II*", + "isCorrect": true + }, + { + "text": "The power-up code in the NES version of *Gradius*", + "isCorrect": true + }, + { + "text": "Skiing downhill in *Starsiege: Tribes*", + "isCorrect": true + }, + { + "text": "An underflow that made Gandhi nuclear in the 1991 *Civilization*", + "isCorrect": false + }, + { + "text": "*Pac-Man*'s broken level 256, which Namco turned into a bonus stage", + "isCorrect": false + } + ], + "explanation": "Five of these glitches were kept, each for its own reason:\n\n- *Space Invaders*: the speed-up made the game tense, and fixing it would have meant fighting the hardware.\n- The Creeper: the broken pig looked creepy, so it became a monster.\n- Canceling: it didn't seem to break anything and it opened up the game, even though the timing looked too hard at first.\n- The Konami code: removing it at the last minute was riskier than leaving it in.\n- Skiing: players built their game around it, so the sequel made it official.\n\nThe Gandhi underflow never existed, as the previous Question showed.\n\n*Pac-Man*'s level 256 is a real bug, but Namco never turned it into a bonus. An integer overflow makes that level load wrongly, and nobody can finish it. It's the same wraparound as in the Gandhi image, only upward: in 8 bits, 255 + 1 wraps to 0. Players call it a *kill screen*, because the game ends there.\n\nIn every kept case, someone decided the behavior was worth more than the fix. Keeping a bug is a design decision, just like fixing one.", + "references": "[Wikipedia: Space Invaders](https://en.wikipedia.org/wiki/Space_Invaders), [Wikipedia: Creeper (Minecraft)](https://en.wikipedia.org/wiki/Creeper_(Minecraft)), [Game Developer: Street Fighter II designer opens up about the canceling bug (2013)](https://www.gamedeveloper.com/business/-i-street-fighter-ii-i-designer-opens-up-about-the-cancelling-bug-), [Wikipedia: Konami Code](https://en.wikipedia.org/wiki/Konami_Code), [Wikipedia: Starsiege: Tribes](https://en.wikipedia.org/wiki/Starsiege:_Tribes), [Wikipedia: Pac-Man](https://en.wikipedia.org/wiki/Pac-Man), [Wikipedia: Nuclear Gandhi](https://en.wikipedia.org/wiki/Nuclear_Gandhi)" + } + ] +} diff --git a/content/quizzes/game-bugs-that-became-features/pig-model-as-planned-and-as-loaded.svg b/content/quizzes/game-bugs-that-became-features/pig-model-as-planned-and-as-loaded.svg new file mode 100644 index 0000000..baa9edf --- /dev/null +++ b/content/quizzes/game-bugs-that-became-features/pig-model-as-planned-and-as-loaded.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + A pig's body: as planned, and as it loaded + Plain boxes for the body. No head, legs or textures. + + + + + + + + + + length + + height + As planned + + + + + + + + length + + height + As it loaded + diff --git a/content/quizzes/game-bugs-that-became-features/space-invaders-frame-time.svg b/content/quizzes/game-bugs-that-became-features/space-invaders-frame-time.svg new file mode 100644 index 0000000..58c84a1 --- /dev/null +++ b/content/quizzes/game-bugs-that-became-features/space-invaders-frame-time.svg @@ -0,0 +1,67 @@ + + + + + Fewer aliens, shorter frames, faster march + Each frame draws the aliens that are left, then moves the formation one small step. + Aliens left + Time to draw one frame + Steps per second + 55 + + + 30 + + + 10 + + + 1 + + + + The step size never changes. Only the number of steps per second does. + Illustrative bar lengths, not measured timings. + diff --git a/content/quizzes/game-bugs-that-became-features/unsigned-8-bit-underflow-wheel.svg b/content/quizzes/game-bugs-that-became-features/unsigned-8-bit-underflow-wheel.svg new file mode 100644 index 0000000..ac68c6c --- /dev/null +++ b/content/quizzes/game-bugs-that-became-features/unsigned-8-bit-underflow-wheel.svg @@ -0,0 +1,93 @@ + + + + + An unsigned 8-bit counter wraps around + 256 values on a ring, 0 to 255. Dots stand in for the values in between. + + + 0 + + 1 + + 2 + + + 64 + + + + + 128 + + + + + 192 + + + 254 + + 255 + + + + + + + 1 − 2: two steps back + 8 bits + 2⁸ = 256 values + + Unsigned 8-bit (0 to 255) + 1 − 1 = 0 + 0 − 1 wraps to the top: 255 + So 1 − 2 = 255 + + Signed 8-bit (−128 to 127) + Same bits, read differently: + the pattern for 255 means −1 + So 1 − 2 = −1, as expected + diff --git a/content/quizzes/have-you-tried-turning-it-off-and-on-again.json b/content/quizzes/have-you-tried-turning-it-off-and-on-again.json new file mode 100644 index 0000000..64c22ba --- /dev/null +++ b/content/quizzes/have-you-tried-turning-it-off-and-on-again.json @@ -0,0 +1,452 @@ +{ + "schemaVersion": 1, + "id": "have-you-tried-turning-it-off-and-on-again", + "title": "Have you tried turning it off and on again? Real bugs a reboot hides", + "description": "On *The IT Crowd*, a reboot is a joke. For the Boeing 787, it was an FAA order. Each Question looks at a real system that failed because it ran too long, from Windows 95 to a Patriot missile battery, and asks why a restart hides the bug without fixing it. A few Questions need only a calculator.", + "language": "en", + "tags": ["debugging", "engineering", "mathematics"], + "author": "Quizbun", + "questions": [ + { + "id": "tick-count-wrap-days", + "title": "Windows 95 counted milliseconds since boot in a 32-bit unsigned integer. After how many days of uptime does that count wrap back to zero?", + "description": "A 32-bit unsigned integer holds 2³² = 4,294,967,296 values, from 0 to 4,294,967,295. A day has 86,400,000 milliseconds. Answer in days, to one decimal place.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [49.7], + "tolerance": 0.05 + }, + "explanation": "4,294,967,296 ms ÷ 86,400,000 ms per day = 49.71 days. One millisecond after 4,294,967,295, the count has nowhere to carry the extra 1, so it rolls over to 0. A car odometer does the same when it goes from 99,999 to 00,000.\n\nMicrosoft's support article KB216641 is called \"Computer Hangs After 49.7 Days\" and covers Windows 95 and Windows 98. It blames \"a timing algorithm in the Vtdapi.vxd file\" and never says how wide the counter is. But 49.7 days is exactly 2³² milliseconds, so the usual explanation is a 32-bit millisecond counter wrapping to zero. Code that assumed the count only goes up could then wait forever for a moment that had already passed.", + "images": [ + { + "src": "counter-rollover-odometer.svg", + "alt": "Two counters rolling over. A five-digit car odometer reading 99999 gains one kilometre and shows 00000. Below it, a 32-bit binary tick count of all ones, 4,294,967,295 milliseconds, gains one millisecond and becomes all zeros, 0 milliseconds. A note works out 4,294,967,296 ms divided by 86,400,000 ms per day as about 49.7 days of uptime.", + "caption": "Decimal or binary, the carry out of the top digit is lost. Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "references": "[Microsoft KB216641: Computer Hangs After 49.7 Days (archived)](https://web.archive.org/web/20061116104241/http://support.microsoft.com/kb/216641), [Microsoft Learn: GetTickCount function](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount)" + }, + { + "id": "why-uptime-bug-hid", + "title": "Windows 95 shipped in 1995, and the 49.7-day hang was documented only years later. What does this bug need before it can happen at all?", + "type": "single-choice", + "options": [ + { + "text": "49.7 days without a single restart, because every boot sets the count back to zero", + "isCorrect": true + }, + { + "text": "A particular date on the calendar, as with the Y2K bug", + "isCorrect": false + }, + { + "text": "More memory than a typical 1990s PC had installed", + "isCorrect": false + }, + { + "text": "A heavy workload that makes the counter tick faster", + "isCorrect": false + } + ], + "explanation": "The counter measures **uptime**, the time since the last boot. It ticks once per millisecond however busy the machine is, and any shutdown, crash or restart puts it back to zero. A PC that was switched off at night, or that crashed for some other reason, never got near day 49.7.\n\nSo every reboot hid the bug. The code was wrong from day one, but the fault needed seven weeks of nonstop running to appear, and ordinary use and testing rarely got that far.\n\nA date bug, like Y2K, hits every machine at the same moment, no matter when each one was started. Memory size and workload don't change how fast a millisecond counter moves.", + "references": "[Microsoft KB216641: Computer Hangs After 49.7 Days (archived)](https://web.archive.org/web/20061116104241/http://support.microsoft.com/kb/216641), [Wikipedia: Software aging](https://en.wikipedia.org/wiki/Software_aging)" + }, + { + "id": "wrap-safe-comparison", + "title": "`GetTickCount()` returns the milliseconds since boot as a 32-bit unsigned `DWORD`. Which timeout check keeps working when that count wraps to zero?", + "description": "Both checks should call `timeOut()` once 5 seconds have passed since `start`.\n\n```c\nDWORD start = GetTickCount();\n/* ... later ... */\n\n// Check A\nif (GetTickCount() >= start + 5000) timeOut();\n\n// Check B\nif (GetTickCount() - start >= 5000) timeOut();\n```", + "type": "single-choice", + "options": [ + { + "text": "Check B, which subtracts `start` from the current count", + "isCorrect": true + }, + { + "text": "Check A, which adds the delay to `start`", + "isCorrect": false + }, + { + "text": "Both, because unsigned arithmetic cannot overflow", + "isCorrect": false + }, + { + "text": "Neither; only `GetTickCount64` can measure an interval across a wrap", + "isCorrect": false + } + ], + "explanation": "Unsigned 32-bit arithmetic wraps modulo 2³² = 4,294,967,296. That breaks Check A and saves Check B.\n\nSay `start = 4,294,965,000`, about 2.3 seconds before the wrap. In Check A, `start + 5000` wraps to 2,704. A moment later `GetTickCount()` returns 4,294,966,000, which is already `>= 2,704`, so the timeout fires almost 5 seconds early.\n\nIn Check B, 5 seconds later the count has wrapped to 2,704. `2,704 - 4,294,965,000` wraps too, and the result is exactly 5,000. Subtracting first measures the **distance** between two readings. That distance stays correct across one wrap, as long as the interval is shorter than 49.7 days.\n\nMicrosoft's documentation suggests two fixes: use `GetTickCount64`, or \"check for an overflow condition when comparing times\". Subtracting first is the simplest way to do the second.", + "references": "[Microsoft Learn: GetTickCount function](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount), [Wikipedia: Integer overflow](https://en.wikipedia.org/wiki/Integer_overflow)" + }, + { + "id": "start-counter-near-wrap", + "title": "Linux starts its 32-bit `jiffies` tick counter at a value that wraps five minutes after boot, not at zero. Why?", + "type": "single-choice", + "options": [ + { + "text": "So wrap-around bugs show up minutes after every boot instead of after weeks of uptime", + "isCorrect": true + }, + { + "text": "To reserve the first five minutes of ticks for the bootloader", + "isCorrect": false + }, + { + "text": "Because the hardware timer cannot start counting from zero", + "isCorrect": false + }, + { + "text": "To stop attackers from reading the machine's uptime", + "isCorrect": false + } + ], + "explanation": "The kernel source says so. Just above `INITIAL_JIFFIES` in `include/linux/jiffies.h` is the comment \"Have the 32-bit jiffies value wrap 5 minutes after boot so jiffies wrap bugs show up earlier.\"\n\nA wrap bug that needs weeks of uptime slips past most testing, because test machines get rebooted all the time. Starting the counter just below its maximum turns a rare, slow bug into one that every developer hits within minutes.\n\nWindows uses the same trick. Microsoft's documentation notes that debug (\"checked\") builds of Windows move the tick count forward by about 49 days, so these bugs show up without a long uptime. Neither trick changes the counter. Both change the testing, so the wrap happens where someone will see it.", + "references": "[Linux source: include/linux/jiffies.h](https://github.com/torvalds/linux/blob/master/include/linux/jiffies.h), [Microsoft Learn: GetTickCount function](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount)" + }, + { + "id": "787-after-248-days", + "title": "In 2015 the FAA found that a Boeing 787 left powered on for 248 days straight could lose something. What?", + "type": "single-choice", + "images": [ + { + "src": "boeing-787-landing.webp", + "alt": "A Boeing 787-8 Dreamliner in Kenya Airways livery on final approach against a clear blue sky, landing gear down.", + "caption": "Kenya Airways Boeing 787-8 landing at Amsterdam Schiphol. Photo by Alf van Beem, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:5Y-KZD_Kenya_Airways_Boeing_787-8_Dreamliner_landing_at_Schiphol_AMS-EHAM_pic1.JPG).", + "width": 1400, + "height": 1050 + } + ], + "options": [ + { + "text": "All AC electrical power, because its generator control units could all go into failsafe mode at once", + "isCorrect": true + }, + { + "text": "Thrust on both engines, because the engine controllers would restart together", + "isCorrect": false + }, + { + "text": "Its flight-management clock, which would reset to the aircraft's delivery date", + "isCorrect": false + }, + { + "text": "Only the cabin entertainment system; the order was a precaution", + "isCorrect": false + } + ], + "explanation": "Airworthiness Directive 2015-09-07 says a 787 \"that has been powered continuously for 248 days can lose all alternating current (AC) electrical power due to the generator control units (GCUs) simultaneously going into failsafe mode.\" It gives the cause in one line: \"a software counter internal to the GCUs that will overflow after 248 days of continuous power.\" The FAA warned this \"could result in loss of control of the airplane.\"\n\nNote the word *simultaneously*. The 787 has four main GCUs so that one failure can't take out the electrics. But all four power up at the same moment, so their counters stay in step and overflow together. Redundancy protects against independent failures, and this one wasn't independent.\n\nThe FAA's fix came straight from the IT help desk: switch it off and on again. Operators had to fully power the aircraft down at least every 120 days, as a stopgap while Boeing wrote new GCU software.", + "references": "[Federal Register: Airworthiness Directives; The Boeing Company Airplanes (AD 2015-09-07)](https://www.federalregister.gov/documents/2015/05/01/2015-10066/airworthiness-directives-the-boeing-company-airplanes)" + }, + { + "id": "gcu-counter-days", + "title": "Suppose the 787's generator counter is a signed 32-bit integer that counts hundredths of a second. After how many days does it overflow?", + "description": "A signed 32-bit integer overflows after 2³¹ = 2,147,483,648 counts. A day has 8,640,000 hundredths of a second. Answer in days, to one decimal place.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [248.55], + "tolerance": 0.06 + }, + "explanation": "2,147,483,648 ÷ 8,640,000 = 248.55 days, which matches the 248 days in the FAA's directive.\n\nThe match is our own inference. The directive says only that \"a software counter internal to the GCUs\" overflows after 248 days of continuous power. It gives neither the counter's width nor its unit. A signed 32-bit count of hundredths of a second is the simplest design that fits the number, so it's the usual explanation.\n\nYou can run the same check on any \"fails after N days\" report. Divide 2³¹ or 2³² by the number of ticks in a day for a few common tick sizes, and see whether one lands on N.", + "references": "[Federal Register: Airworthiness Directives; The Boeing Company Airplanes (AD 2015-09-07)](https://www.federalregister.gov/documents/2015/05/01/2015-10066/airworthiness-directives-the-boeing-company-airplanes)" + }, + { + "id": "787-51-days", + "title": "In 2020 the FAA again ordered 787 operators to cycle the aircraft's electrical power, this time because of a 51-day limit. What could go wrong after 51 days?", + "type": "single-choice", + "options": [ + { + "text": "The check that flags stale data could stop working, so old airspeed, altitude or attitude data could be displayed as current", + "isCorrect": true + }, + { + "text": "The generator control units could fail together, as in 2015", + "isCorrect": false + }, + { + "text": "The navigation database would expire and need reloading", + "isCorrect": false + }, + { + "text": "The flight data recorders would run out of storage", + "isCorrect": false + } + ], + "explanation": "Airworthiness Directive 2020-06-14 says \"the stale-data monitoring function of the common core system (CCS) may be lost when continuously powered on for 51 days.\" Flight-critical data such as airspeed, altitude, attitude and engine readings travels over the 787's common data network, and the monitor checks how old each message is. If the monitor is lost and a network switch also fails, old data could be \"routed and displayed as valid data.\" Among the possible results, the FAA listed misleading attitude, altitude and airspeed on both pilots' displays.\n\nIn a way this is worse than a crash. Pilots notice a frozen display, but an old number that looks plausible can pass unnoticed. The directive requires repeated power cycling on a schedule set by a Boeing service bulletin. Unlike the 2015 directive, it doesn't say what fails inside the system at 51 days, so any specific counter explanation would be a guess.", + "references": "[Federal Register: Airworthiness Directives; The Boeing Company Airplanes (AD 2020-06-14)](https://www.federalregister.gov/documents/2020/03/23/2020-06092/airworthiness-directives-the-boeing-company-airplanes)" + }, + { + "id": "patriot-what-went-wrong", + "title": "On 25 February 1991 a Patriot battery at Dhahran, Saudi Arabia, failed to intercept an Iraqi Scud missile, which hit a US Army barracks and killed 28 soldiers. According to the GAO's investigation, what went wrong?", + "type": "single-choice", + "options": [ + { + "text": "Converting the system's clock to seconds lost precision, and the error grew the longer the system ran", + "isCorrect": true + }, + { + "text": "The clock counter overflowed and reset to zero while the Scud was being tracked", + "isCorrect": false + }, + { + "text": "The radar overheated after days of continuous operation", + "isCorrect": false + }, + { + "text": "Iraqi forces jammed the battery's radar", + "isCorrect": false + } + ], + "explanation": "The GAO found \"an inaccurate tracking calculation that became worse the longer the system operated.\" The clock counted time in whole tenths of a second. To predict where a Scud would appear next, the software converted that count to seconds, and the Patriot's 24-bit registers made the conversion slightly off. The longer the system ran, the bigger the error.\n\nAfter the radar first spots a target, the Patriot looks for it again inside a \"range gate\" around the predicted position. With the clock off, the gate was in the wrong place. The Dhahran battery had been running for more than 100 hours, and the Scud wasn't where it looked.\n\nNothing overflowed. The count itself was correct; the multiplication that turned it into seconds drifted. The Patriot was designed to move often and run a few hours at a time. At Dhahran it had been defending one site for days.", + "references": "[GAO: Patriot Missile Defense: Software Problem Led to System Failure at Dhahran, Saudi Arabia (IMTEC-92-26, 1992)](https://www.gao.gov/products/imtec-92-26)" + }, + { + "id": "patriot-drift-seconds", + "title": "Each tenth-of-a-second tick of the Patriot's clock was converted with an error of about 0.000000095 seconds. How many seconds off was the clock after 100 hours of continuous running?", + "description": "100 hours is 360,000 seconds, or 3,600,000 ticks. Answer in seconds, to two decimal places.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [0.34], + "tolerance": 0.005 + }, + "explanation": "3,600,000 ticks × 0.000000095 s ≈ 0.34 s. The GAO's own table gives 0.3433 seconds at 100 hours.\n\nThe tiny per-tick error comes from the number 0.1. In binary, one tenth never ends: 0.000110011001100110011… repeats forever, just as 1/3 does in decimal. The Patriot stored it in a 24-bit register, which cut the pattern short. Each time the software multiplied the tick count by that shortened 0.1, the result came out slightly low, and the gap grew with the count.\n\nA third of a second sounds small, but a Scud flies at about Mach 5. The GAO calculated that after 100 hours the range gate had shifted 687 metres. After about 20 hours of running, an incoming Scud could already fall outside the gate. The chart plots the GAO's figures.", + "images": [ + { + "src": "patriot-clock-drift.svg", + "alt": "Line chart of the Patriot's timing error against hours of continuous operation, from 0 to 100 hours. The error rises in a straight line from 0 to 0.34 seconds. Points are labelled with the range-gate shift: 137 metres at 20 hours, 330 metres at 48 hours, 494 metres at 72 hours. A dashed line at about 20 hours marks where the Scud falls outside the range gate. A callout at 100 hours reads Dhahran, over 100 hours, 0.34 seconds off, gate moved 687 metres. A note says rebooting set the clock, and the error, back to zero.", + "caption": "Figures from GAO report IMTEC-92-26, Appendix II. Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 460 + } + ], + "references": "[GAO: Patriot Missile Defense: Software Problem Led to System Failure at Dhahran, Saudi Arabia (IMTEC-92-26, 1992)](https://www.gao.gov/products/imtec-92-26), [Robert Skeel: Roundoff Error and the Patriot Missile (SIAM News, 1992)](https://www-users.cse.umn.edu/~arnold/disasters/Patriot-dharan-skeel-siam.pdf)" + }, + { + "id": "patriot-report-findings", + "title": "Which of these statements about the Dhahran failure appear in the GAO's report?", + "type": "multiple-choice", + "options": [ + { + "text": "Rebooting the system every few hours would have reset the clock and removed the range-gate shift", + "isCorrect": true + }, + { + "text": "The Army warned users that \"very long run times\" could shift the range gate, but didn't say how long that was", + "isCorrect": true + }, + { + "text": "Corrected software reached Dhahran the day after the attack", + "isCorrect": true + }, + { + "text": "The Army had ordered a reboot every 8 hours, and the Dhahran battery ignored the order", + "isCorrect": false + }, + { + "text": "Nobody had seen the accuracy problem before 25 February 1991", + "isCorrect": false + } + ], + "explanation": "The report says the range-gate shift \"could be eliminated by rebooting the system—turning the system off and on—every few hours.\" A reboot took 60 to 90 seconds and set the clock, and the error with it, back to zero.\n\nThe problem wasn't new. Israeli users had reported a loss of accuracy after 8 hours of continuous running. The Army thought that was unusual and assumed other users didn't run their systems that long. On 21 February 1991 it warned that \"very long run times\" could shift the range gate, but it never said how long was very long. Army officials told the GAO they assumed nobody would run a battery long enough to fail, so they saw no need for more detailed guidance. Fixed software that corrected the timing error reached Dhahran on 26 February, the day after the attack.\n\nA known workaround only protects people if it says exactly when to use it. \"Very long run times\" gave the crew at Dhahran nothing to act on.", + "references": "[GAO: Patriot Missile Defense: Software Problem Led to System Failure at Dhahran, Saudi Arabia (IMTEC-92-26, 1992)](https://www.gao.gov/products/imtec-92-26)" + }, + { + "id": "sixteen-bit-wrap-seconds", + "title": "A small embedded device counts milliseconds since power-on in an unsigned 16-bit integer. After how many seconds does the count wrap to zero?", + "description": "Answer in seconds, to three decimal places.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [65.536], + "tolerance": 0.0005 + }, + "explanation": "16 bits hold 2¹⁶ = 65,536 values, so the count covers 0 to 65,535 ms and wraps after 65,536 ms, or 65.536 seconds.\n\nA counter this small is almost lucky. It wraps about once a minute, so code that mishandles the wrap fails in the first bench test, and the developer fixes it. Widen the same counter to 32 bits and the wrap moves to day 49.7, well past most tests. The bug is the same; only the wait is longer.\n\nYou'll see this throughout the Quiz. A bigger counter makes a wrap bug rarer without removing it, and each reboot pushes it further away.", + "references": "[Wikipedia: Integer overflow](https://en.wikipedia.org/wiki/Integer_overflow)" + }, + { + "id": "settimeout-max-delay", + "title": "In a browser, what does `setTimeout(sendReminder, 30 * 24 * 60 * 60 * 1000)` do?", + "description": "The delay is 30 days in milliseconds: 2,592,000,000.", + "type": "single-choice", + "options": [ + { + "text": "Calls `sendReminder` almost immediately", + "isCorrect": true + }, + { + "text": "Calls `sendReminder` after 30 days", + "isCorrect": false + }, + { + "text": "Caps the delay at about 24.8 days and calls `sendReminder` then", + "isCorrect": false + }, + { + "text": "Throws a `RangeError` because the delay is too large", + "isCorrect": false + } + ], + "explanation": "Browsers convert the delay to a signed 32-bit integer, so the longest delay they can represent is 2,147,483,647 ms, about 24.8 days. 2,592,000,000 is larger, so it wraps: 2,592,000,000 − 4,294,967,296 = −1,702,967,296. A negative delay counts as zero, so the callback runs as soon as the browser gets to it.\n\nMDN covers this under \"Maximum delay value\". In its examples, `2 ** 32 - 5000` runs immediately and `2 ** 32 + 5000` runs after about 5 seconds, because the wrap drops whole multiples of 2³². Node.js doesn't wrap at all: it runs any timeout above 2,147,483,647 ms immediately.\n\nThere's no error and no cap. To wait longer, save the target time and check it with a shorter timer, or use a scheduler outside the page.", + "references": "[MDN: setTimeout() — Maximum delay value](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout#maximum_delay_value)" + }, + { + "id": "signed-32-bit-max", + "title": "In December 2014 the view count of PSY's \"Gangnam Style\" passed the largest value a signed 32-bit integer can hold. Type that value.", + "description": "Digits only, without commas or spaces.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [2147483647] + }, + "explanation": "The value is 2³¹ − 1 = 2,147,483,647.\n\nA signed 32-bit integer uses its first bit for the sign. That leaves 31 bits and 2³¹ = 2,147,483,648 non-negative values. Zero takes one of them, so the largest positive value is one less. The negative side gets the spare value, so its minimum is −2,147,483,648.\n\nYou'll meet this number often. It's `Integer.MAX_VALUE` in Java, the longest browser `setTimeout` delay in milliseconds, and the largest value a signed 32-bit database column can hold. A counter sitting at it is one increment from trouble.", + "references": "[Guinness World Records: First YouTube video to require a 64-bit counter](https://www.guinnessworldrecords.com/world-records/381953-first-youtube-video-to-require-a-64-bit-counter), [Wikipedia: 2,147,483,647](https://en.wikipedia.org/wiki/2,147,483,647)" + }, + { + "id": "gangnam-counter", + "title": "\"Gangnam Style\" passed 2,147,483,647 views in December 2014. What happened to YouTube's view counter?", + "type": "single-choice", + "options": [ + { + "text": "Nothing broke: YouTube had moved the counter to a 64-bit integer months earlier", + "isCorrect": true + }, + { + "text": "It showed a negative view count until engineers patched it", + "isCorrect": false + }, + { + "text": "It froze at 2,147,483,647 for several days", + "isCorrect": false + }, + { + "text": "The video was taken down while YouTube migrated its database", + "isCorrect": false + } + ], + "videos": [ + { + "provider": "youtube", + "id": "9bZkp7q19f0", + "placement": "explanation" + } + ], + "explanation": "YouTube announced the milestone as a joke. Its Google+ post said: \"We never thought a video would be watched in numbers greater than a 32-bit integer (=2,147,483,647 views), but that was before we met PSY.\" Many outlets reported that the video had broken YouTube. A spokesperson then told Variety \"we updated the counter to 64-bit months ago\" and called the post an Easter egg.\n\nSo this one is a success story. Someone saw a counter nearing its limit and widened it before it overflowed. That's a real fix; a restart wouldn't have helped. Guinness World Records lists \"Gangnam Style\" as the first YouTube video to require a 64-bit counter.\n\nHere's the video that did it.", + "references": "[Variety: No, Psy's 'Gangnam Style' Did Not Break YouTube Video Counter (2014)](https://variety.com/2014/digital/news/no-psys-gangnam-style-did-not-break-youtube-video-counter-1201371888/), [Guinness World Records: First YouTube video to require a 64-bit counter](https://www.guinnessworldrecords.com/world-records/381953-first-youtube-video-to-require-a-64-bit-counter)" + }, + { + "id": "overflow-goes-negative", + "title": "Suppose YouTube had kept a signed 32-bit view counter that wraps on overflow. What would it show after one more view past 2,147,483,647?", + "type": "single-choice", + "options": [ + { + "text": "−2,147,483,648", + "isCorrect": true + }, + { + "text": "0", + "isCorrect": false + }, + { + "text": "2,147,483,648", + "isCorrect": false + }, + { + "text": "−1", + "isCorrect": false + } + ], + "explanation": "Almost all hardware stores signed integers in **two's complement**. The first bit is the sign, and a pattern with that bit set means its unsigned value minus 2³². The largest positive value is `0111…1111`. Add 1 and the carry turns it into `1000…0000`, the most negative value, −2,147,483,648. The ring in the diagram shows the jump.\n\n0 is where an *unsigned* counter goes after its maximum, like the Windows 95 tick count. −1 is the all-ones pattern, `1111…1111`, just below zero on the same ring. 2,147,483,648 would need a 33rd bit.\n\nLanguages handle this differently. Java wraps as shown. In C and C++, signed overflow is undefined behavior, so the compiler may assume it never happens, and the program can do something even stranger.", + "images": [ + { + "src": "signed-32-bit-wrap.svg", + "alt": "A ring of all 2 to the 32 values of a signed 32-bit integer. 0 is at the top, with 1 just clockwise and -1 just counterclockwise. Positive values run clockwise down the right side through plus 1 billion to 2,147,483,647 near the bottom. An arrow labelled plus 1 jumps from there to its neighbour, -2,147,483,648, and negative values continue up the left side through minus 1 billion back to -1. A side panel lists the bit patterns: 0000...0001 is 1, 0111...1111 is 2,147,483,647, 1000...0000 is -2,147,483,648 and 1111...1111 is -1, noting that the first bit is the sign.", + "caption": "The jump from the largest value to the most negative one is a single step. Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 460 + } + ], + "references": "[Wikipedia: Two's complement](https://en.wikipedia.org/wiki/Two%27s_complement), [Wikipedia: Integer overflow](https://en.wikipedia.org/wiki/Integer_overflow). For data that really did break software, see the Quiz \"Names that break software: NULL, True and little Bobby Tables\"." + }, + { + "id": "years-to-fill-64-bits", + "title": "YouTube's counter can now reach 9,223,372,036,854,775,807, the largest signed 64-bit value. If a video gained 1 billion views every day, how many million years would it take to reach that?", + "description": "Use 365.25 days per year. Answer in millions of years, to one decimal place.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [25.3], + "tolerance": 0.5 + }, + "explanation": "9,223,372,036,854,775,807 ÷ 1,000,000,000 views per day ≈ 9.22 billion days. Divided by 365.25, that's about 25.3 million years.\n\nThat's what widening a counter buys you. Each extra bit doubles the range, so 32 more bits multiply it by about 4.3 billion. A limit one video reached in two and a half years now lies beyond the life of the hardware, the company and probably the human species.\n\nWindows got the same fix. `GetTickCount64` counts milliseconds in 64 bits and won't wrap for about 584 million years of uptime.", + "references": "[Guinness World Records: First YouTube video to require a 64-bit counter](https://www.guinnessworldrecords.com/world-records/381953-first-youtube-video-to-require-a-64-bit-counter), [Microsoft Learn: GetTickCount64 function](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64)" + }, + { + "id": "uptime-bug-symptoms", + "title": "A fleet of identical machines hangs now and then. Which observations point to an uptime bug, one that depends on time since the last boot?", + "type": "multiple-choice", + "options": [ + { + "text": "Machines that were powered on at the same time fail at the same time, whatever their workload", + "isCorrect": true + }, + { + "text": "After a restart, a machine runs fine for the same length of time before the fault returns", + "isCorrect": true + }, + { + "text": "The time to failure matches a power of two in some unit, such as 49.7 days or 65.5 seconds", + "isCorrect": true + }, + { + "text": "Every machine fails at the same moment on one calendar date, whenever it was last started", + "isCorrect": false + }, + { + "text": "Failures happen only during peak traffic and stop when the load drops", + "isCorrect": false + } + ], + "explanation": "An uptime bug runs on a stopwatch that starts at boot. Machines started together reach the limit together, which is why the 787's four generator control units could fail at once. A restart resets the stopwatch, so the fault comes back after the same interval every time. When that interval is 2ⁿ ticks of some unit, a counter is almost certainly wrapping. A quick division, like the ones earlier in this Quiz, often tells you which counter.\n\nThe other two point elsewhere. A failure that hits every machine at the same clock time, however long each has been up, is a **date** bug: it reads the calendar. Failures that follow load point to capacity limits, lock contention or timeouts under pressure.\n\nA slow memory leak also looks \"fine for a while after each restart\", but its interval changes with the workload. A wrap bug fails on a fixed schedule.", + "references": "[Wikipedia: Software aging](https://en.wikipedia.org/wiki/Software_aging), [Federal Register: AD 2015-09-07](https://www.federalregister.gov/documents/2015/05/01/2015-10066/airworthiness-directives-the-boeing-company-airplanes)" + }, + { + "id": "fix-or-reset", + "title": "Which of these changes fix the underlying bug, rather than restart the clock on it?", + "type": "multiple-choice", + "options": [ + { + "text": "Replacing `GetTickCount` with `GetTickCount64`", + "isCorrect": true + }, + { + "text": "Measuring elapsed time as `now - start` in unsigned arithmetic, instead of testing `now >= start + delay`", + "isCorrect": true + }, + { + "text": "Moving YouTube's view counter from 32 to 64 bits", + "isCorrect": true + }, + { + "text": "Powering down every 787 at least every 120 days", + "isCorrect": false + }, + { + "text": "Rebooting the Patriot battery every few hours", + "isCorrect": false + }, + { + "text": "Scheduling a nightly restart for a server that leaks memory", + "isCorrect": false + } + ], + "explanation": "A fix changes the code so the failure can't happen, or can't happen in any realistic lifetime. `GetTickCount64` and a 64-bit view counter push the limit out by millions of years. Unsigned subtraction keeps the arithmetic correct across a wrap, so the counter can roll over as often as it likes.\n\nThe other three are **workarounds**. Each one resets the state before the bug has time to show up, and the bug stays in the code. Sometimes that's the right call. The FAA called its 120-day power-down \"interim action\" while Boeing wrote new software, and the GAO said a reboot every few hours would have kept the Patriot accurate. Engineers even have a name for planned restarts: software rejuvenation.\n\nWorkarounds have two risks. They only work if people follow them, and the Patriot's operators were never told how often was often enough. They also hide the evidence, so the next team may never learn the bug is there.\n\nCurious how long your own computer has been running? Type `uptime` on macOS or Linux, or open the Performance tab of Task Manager on Windows.", + "references": "[Microsoft Learn: GetTickCount64 function](https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-gettickcount64), [Wikipedia: Software aging](https://en.wikipedia.org/wiki/Software_aging). For why a reboot clears leaked memory, see the Quiz \"The IT Crowd: have you tried turning it off and on again?\"." + } + ] +} diff --git a/content/quizzes/have-you-tried-turning-it-off-and-on-again/boeing-787-landing.webp b/content/quizzes/have-you-tried-turning-it-off-and-on-again/boeing-787-landing.webp new file mode 100644 index 0000000..495b6d1 Binary files /dev/null and b/content/quizzes/have-you-tried-turning-it-off-and-on-again/boeing-787-landing.webp differ diff --git a/content/quizzes/have-you-tried-turning-it-off-and-on-again/counter-rollover-odometer.svg b/content/quizzes/have-you-tried-turning-it-off-and-on-again/counter-rollover-odometer.svg new file mode 100644 index 0000000..d790d78 --- /dev/null +++ b/content/quizzes/have-you-tried-turning-it-off-and-on-again/counter-rollover-odometer.svg @@ -0,0 +1,125 @@ + + + + + + + + + + A counter that runs out of digits starts again at zero + Add 1 to the largest value and every digit carries. The last carry has nowhere to go. + Car odometer, 5 decimal digits + + 9 + + 9 + + 9 + + 9 + + 9 + + +1 km + + 0 + + 0 + + 0 + + 0 + + 0 + 99,999 km + reads as 0 km + Windows 95 tick count: 32 bits, one tick per millisecond + + 1 + + 1 + + 1 + + 1 + … + + 1 + + 1 + + 1 + + 1 + + +1 ms + + 0 + + 0 + + 0 + + 0 + … + + 0 + + 0 + + 0 + + 0 + 4,294,967,295 ms + 0 ms + + 32 bits hold 2³² = 4,294,967,296 values, 0 to 4,294,967,295. + 4,294,967,296 ms ÷ 86,400,000 ms per day ≈ 49.7 days of uptime, then back to zero. + diff --git a/content/quizzes/have-you-tried-turning-it-off-and-on-again/patriot-clock-drift.svg b/content/quizzes/have-you-tried-turning-it-off-and-on-again/patriot-clock-drift.svg new file mode 100644 index 0000000..9e65108 --- /dev/null +++ b/content/quizzes/have-you-tried-turning-it-off-and-on-again/patriot-clock-drift.svg @@ -0,0 +1,95 @@ + + + + + + + + + + The Patriot's clock error grew with every hour it ran + Timing error and range-gate shift by hours of continuous running (GAO figures) + + 0.0 s + + 0.1 s + + 0.2 s + + 0.3 s + + 0.4 s + 0 + 20 + 40 + 60 + 80 + 100 + Hours since the system was last started + + + + + + + + + + 137 m + 330 m + 494 m + + About 20 h: the Scud falls + outside the range gate + + Dhahran, over 100 h + 0.34 s off, gate moved 687 m + + Labels on the line show how far the range gate moved. A reboot set the clock, and the error, back to zero. + diff --git a/content/quizzes/have-you-tried-turning-it-off-and-on-again/signed-32-bit-wrap.svg b/content/quizzes/have-you-tried-turning-it-off-and-on-again/signed-32-bit-wrap.svg new file mode 100644 index 0000000..4ff4d24 --- /dev/null +++ b/content/quizzes/have-you-tried-turning-it-off-and-on-again/signed-32-bit-wrap.svg @@ -0,0 +1,90 @@ + + + + + + + + + + A signed 32-bit counter wraps from the top to the bottom + In two's complement the values form a ring. One step past the largest is the most negative. + + + 0 + + −1 + + 1 + + +1 billion + + −1 billion + + 2,147,483,647 + + −2,147,483,648 + + +1 + 2³² values + on one ring + + Same 32 bits, read as signed + 0000…0001 + 1 + 0111…1111 + 2,147,483,647 + 1000…0000 + −2,147,483,648 + 1111…1111 + −1 + The first bit is the sign. Adding 1 + to 0111…1 carries into it and flips the sign. + Only a few points are labelled. The ring holds every value in between. + diff --git a/content/quizzes/hollywood-hacking-real-or-nonsense.json b/content/quizzes/hollywood-hacking-real-or-nonsense.json new file mode 100644 index 0000000..2d0ac1f --- /dev/null +++ b/content/quizzes/hollywood-hacking-real-or-nonsense.json @@ -0,0 +1,403 @@ +{ + "schemaVersion": 1, + "id": "hollywood-hacking-real-or-nonsense", + "title": "Hollywood hacking: real or nonsense?", + "description": "Some movie hacks use real tools on real vulnerabilities. Others are technobabble written on deadline. Each Question takes a famous scene, whether a screen, a command or a line of dialogue, and asks if it would work. The Explanations cover the real tech behind it: a 3D file browser, port scans, an SSH bug, private IP addresses, and why \"Enhance!\" can't add detail the camera never captured.", + "language": "en", + "tags": ["security", "networking", "critical-thinking", "internet-culture"], + "author": "Quizbun", + "questions": [ + { + "id": "jurassic-park-unix-system", + "title": "\"It's a UNIX system! I know this!\" What is the 3D interface Lex uses in *Jurassic Park*?", + "description": "In the 1993 film, Lex sits down at a computer that shows a landscape of glowing blocks on pedestals. She flies through it, finds what she needs and locks the control room doors just in time.", + "type": "single-choice", + "videos": [ + { + "provider": "youtube", + "id": "JOeY07qKU9c", + "placement": "explanation" + } + ], + "options": [ + { + "text": "A real file manager called fsn, made by Silicon Graphics for IRIX, its version of Unix", + "isCorrect": true + }, + { + "text": "An animation the effects studio made to look like a computer, with no real software behind it", + "isCorrect": false + }, + { + "text": "A flight simulator the set designers reskinned to look like a file system", + "isCorrect": false + }, + { + "text": "A mock-up of a virtual reality headset view, projected onto the monitor", + "isCorrect": false + } + ], + "explanation": "People love to mock this scene, yet it's one of the most accurate in this Quiz. The program is **fsn**, short for File System Navigator and pronounced \"fusion\". Silicon Graphics made it as an experimental 3D file manager for IRIX, its version of Unix. The park's control room runs on SGI workstations, so this is software those machines really ran.\n\nIt looks fake because almost nobody used fsn day to day. Most Unix users of the time worked in a text terminal. Lex is still right. Under the 3D landscape is an ordinary Unix directory tree, and the blocks are its files.", + "references": "[SGI Silicon Surf archive: 3D File System Navigator for IRIX](https://archive.irixnet.org/siliconsurf/free/cool_sw_01.html)\n\n[Wikipedia: File System Visualizer](https://en.wikipedia.org/wiki/File_System_Visualizer), an open-source clone of fsn" + }, + { + "id": "fsn-tall-box", + "title": "In fsn, each directory is a pedestal with boxes standing on it. What does an unusually tall box tell you?", + "type": "single-choice", + "options": [ + { + "text": "That file is large", + "isCorrect": true + }, + { + "text": "That file was changed recently", + "isCorrect": false + }, + { + "text": "Only the root user can open that file", + "isCorrect": false + }, + { + "text": "That program is running right now", + "isCorrect": false + } + ], + "explanation": "Each box is one file, and **its height is the file's size**. A directory's pedestal grows with the total size of its files, so big directories stand out from across the landscape.\n\nAge shows up too, as colour instead of height, which makes \"changed recently\" a near miss. Permissions and running programs don't show up at all, because fsn maps the file system, not what the machine is doing.\n\nDisk-usage tools still use the idea. Treemap viewers draw each file as a rectangle sized by its bytes, because size is what you want to see first when a disk fills up.", + "references": "[SGI Silicon Surf archive: 3D File System Navigator for IRIX](https://archive.irixnet.org/siliconsurf/free/cool_sw_01.html)" + }, + { + "id": "ssh-default-port", + "title": "In *The Matrix Reloaded* (2003), Trinity scans a power station's server and finds exactly one open service: SSH. Which TCP port does an SSH server listen on by default?", + "description": "Type a port number.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [22] + }, + "explanation": "SSH listens on **port 22**. A port number tells the operating system which program gets an incoming connection, such as 22 for SSH, 80 for plain HTTP and 443 for HTTPS. Common services have fixed numbers, so clients know where to connect without asking.\n\nTrinity's scan shows `22/tcp open ssh`, which means port 22, protocol TCP, state open, and the service usually found on that port. Security people love the scene for this line, because it's exactly what a real scan of a server running only SSH prints.", + "references": "[IANA: Service Name and Transport Protocol Port Number Registry](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml)" + }, + { + "id": "trinity-hack-verdict", + "title": "Here is Trinity's full hack from *The Matrix Reloaded*. How realistic is it?", + "description": "Her screen, shortened:\n\n```text\n# nmap -v -sS -O 10.2.2.2\nStarting nmap V. 2.54BETA25\n22/tcp open ssh\n\n# sshnuke 10.2.2.2 -rootpw=\"Z1ON0101\"\nConnecting to 10.2.2.2:ssh ... successful.\nAttempting to exploit SSHv1 CRC32 ... successful.\nReseting root password to \"Z1ON0101\".\nSystem open: Access Level <9>\n```", + "type": "single-choice", + "options": [ + { + "text": "A real scanner and a real 2001 SSH vulnerability; only the `sshnuke` program is made up", + "isCorrect": true + }, + { + "text": "Completely made up, like most movie hacking", + "isCorrect": false + }, + { + "text": "A real scanner, but the SSH vulnerability is fictional", + "isCorrect": false + }, + { + "text": "Real tools, but these commands only work on Windows", + "isCorrect": false + } + ], + "explanation": "Almost everything checks out. **Nmap 2.54BETA25** was a real release of the free Nmap scanner. \"SSHv1 CRC32\" was a real bug, widely publicised in 2001, in servers that spoke version 1 of the SSH protocol. The steps come in a realistic order too: find what's listening, spot a known weakness, get in.\n\nThe made-up part is the `sshnuke` program with its tidy progress messages. \"Access Level <9>\" is movie flavour as well, since Unix has no numbered access levels.\n\nThe Nmap site says its author was \"shocked to find that Trinity does it properly\", and contrasts the scene with the 3D eye candy of films like *Hackers*. The practical lesson is that the city ran a server nobody had patched.", + "references": "[Nmap: Movies featuring the Nmap security scanner](https://nmap.org/movies/)\n\n[Help Net Security: Matrix hacking, a slice of reality in a world of make believe (2003)](https://www.helpnetsecurity.com/2003/05/21/matrix-hacking-a-slice-of-reality-in-a-world-of-make-believe/)" + }, + { + "id": "nmap-syn-scan", + "title": "Trinity runs `nmap -sS`. What kind of scan is that?", + "type": "single-choice", + "images": [ + { + "src": "syn-scan-half-open.svg", + "alt": "Three panels, each with Nmap on the left and the target on the right. Open port: Nmap sends SYN, the target replies SYN/ACK, and Nmap sends RST, so the port is open. Closed port: Nmap sends SYN and the target replies RST, so the port is closed. Filtered port: Nmap sends SYN, retries, and gets no reply, so the port is filtered. A note below says a normal client answers SYN/ACK with ACK and connects, but the scanner sends RST instead, so the connection never opens. That's a half-open scan.", + "caption": "Based on the Nmap Reference Guide. Diagram: Quizbun.", + "placement": "explanation", + "width": 720, + "height": 456 + } + ], + "options": [ + { + "text": "A SYN scan: it starts a TCP handshake on each port and reads the reply, but never finishes the connection", + "isCorrect": true + }, + { + "text": "A secure scan that encrypts its probes so the target can't read them", + "isCorrect": false + }, + { + "text": "A slow scan that spreads its probes over hours to avoid notice", + "isCorrect": false + }, + { + "text": "An SSH-only scan that looks for SSH servers and ignores other services", + "isCorrect": false + } + ], + "explanation": "`-sS` is a **TCP SYN scan**. A TCP connection normally opens with three packets: SYN, SYN/ACK, ACK. Nmap sends only the first one, a SYN, to each port and reads the reply:\n\n- SYN/ACK means something is listening, so the port is open.\n- RST means nothing is listening, so the port is closed.\n- No reply, even after retries, usually means a firewall dropped the probe, so Nmap reports the port as filtered.\n\nWhen an open port answers, the scanning machine sends RST instead of the final ACK, so the connection never fully opens. That's why it's called a **half-open** scan. It needs raw-packet privileges, and Nmap uses it by default when run as root or Administrator.\n\nThe other flags in Trinity's command are real too. `-v` turns on verbose output, and `-O` asks Nmap to guess the target's operating system.", + "references": "[Nmap Reference Guide: TCP SYN scan (-sS)](https://nmap.org/book/synscan.html)" + }, + { + "id": "private-ip-target", + "title": "Trinity's target is `10.2.2.2`. What does that address tell you about where she has to be?", + "type": "single-choice", + "options": [ + { + "text": "It's a private address, so she has to be on the power station's internal network", + "isCorrect": true + }, + { + "text": "It's an invalid address, a movie fake like a 555 phone number", + "isCorrect": false + }, + { + "text": "It's a public address, so she could scan it from anywhere in the world", + "isCorrect": false + }, + { + "text": "It's the address of the terminal she's sitting at", + "isCorrect": false + } + ], + "explanation": "Every address from `10.0.0.0` to `10.255.255.255` is reserved for **private networks**. Homes, offices and factories reuse these addresses internally, and routers on the public internet don't carry traffic to them. Nobody outside can reach `10.2.2.2`. You have to be on the same internal network.\n\nThat fits the film. Trinity fights her way into the power station and works from a terminal inside the building. The address is valid, so it's not a 555-style fake, and the machine she scans is a separate server, not her own terminal.\n\nMany industrial networks rely on the same idea: keep the control systems off the internet and they're safe. That holds only until someone gets into the building or onto the internal network.", + "references": "[RFC 1918: Address Allocation for Private Internets](https://www.rfc-editor.org/rfc/rfc1918)" + }, + { + "id": "ssh1-crc32-bug", + "title": "Which statements about the real SSH1 CRC32 bug, the one named on Trinity's screen, are true?", + "type": "multiple-choice", + "options": [ + { + "text": "An attacker needed no password, because the attack happened before login", + "isCorrect": true + }, + { + "text": "The attacker's code ran with the SSH server's privileges, which usually meant root", + "isCorrect": true + }, + { + "text": "Fixed versions were available by 2001, about two years before the film", + "isCorrect": true + }, + { + "text": "It only affected Windows servers", + "isCorrect": false + }, + { + "text": "It let attackers read recorded SSH traffic, but not run anything", + "isCorrect": false + }, + { + "text": "It was a flaw in the CRC32 checksum itself, so every version of SSH was affected", + "isCorrect": false + } + ], + "explanation": "The bug was an integer overflow in SSH1 servers, including older releases of OpenSSH and SSH Communications' Secure Shell. Ironically, it sat in code added to *defend* against a known weakness in how SSH1 used CRC32. The flaw triggered while the connection was being set up, before any login.\n\nThe SSH server ran as root, because it has to be able to log in any user, so the attacker's code ran as root too. That makes Trinity's next step, resetting the root password, believable. CERT's advisory lists two fixes: upgrade to a patched release, or turn off protocol version 1. SSH version 2 was designed differently and wasn't affected. The affected servers ran Unix-like systems, not Windows.", + "references": "[CERT/CC: VU#945216, SSH CRC32 attack detection code contains remote integer overflow](https://www.kb.cert.org/vuls/id/945216)" + }, + { + "id": "matrix-police-warning", + "title": "After *The Matrix Reloaded* opened in 2003, who warned British viewers not to copy Trinity's hack?", + "type": "single-choice", + "options": [ + { + "text": "Scotland Yard's Computer Crime Unit and the British Computer Society", + "isCorrect": true + }, + { + "text": "Warner Bros., in a notice at the end of the film's credits", + "isCorrect": false + }, + { + "text": "The OpenSSH developers, in a security advisory", + "isCorrect": false + }, + { + "text": "The BBC, in a disclaimer before its reports on the film", + "isCorrect": false + } + ], + "explanation": "Scotland Yard's Computer Crime Unit and the British Computer Society issued a joint warning. Nmap's site quotes it: viewers \"should not be tempted to emulate the realistic depiction of computer hacking\".\n\nThe warning only made sense because the scene was realistic. Nobody warns audiences about the swirling 3D graphics in *Hackers*. What actually protected servers was administrators installing the fix, which had been out for two years. Only machines still unpatched in 2003 were at risk.", + "references": "[Nmap: Movies featuring the Nmap security scanner](https://nmap.org/movies/)" + }, + { + "id": "nmap-filmography", + "title": "Nmap has appeared in many films since *The Matrix Reloaded*. Which of these films show it on screen?", + "type": "multiple-choice", + "options": [ + { + "text": "*Live Free or Die Hard*", + "isCorrect": true + }, + { + "text": "*The Bourne Ultimatum*", + "isCorrect": true + }, + { + "text": "*Elysium*", + "isCorrect": true + }, + { + "text": "*Ocean's 8*", + "isCorrect": true + }, + { + "text": "*Hackers*", + "isCorrect": false + }, + { + "text": "*Sneakers*", + "isCorrect": false + }, + { + "text": "*WarGames*", + "isCorrect": false + } + ], + "explanation": "Nmap's site keeps a list of films and credits the people who spotted each scene. *Live Free or Die Hard* and *The Bourne Ultimatum* both came out in 2007. In *Bourne*, the CIA uses Zenmap, Nmap's graphical front end. *Elysium* (2013) shows a scan of the brain implant in Matt Damon's character. In *Ocean's 8* (2018), Rihanna's hacker has Nmap running in the background of several scenes.\n\nYou can rule out the other three by date alone. Nmap first appeared in September 1997, as an article with source code in *Phrack* magazine. *WarGames* (1983), *Sneakers* (1992) and *Hackers* (1995) all came out before it existed.", + "references": "[Nmap: Movies featuring the Nmap security scanner](https://nmap.org/movies/)\n\n[Phrack 51: The Art of Port Scanning (1997)](http://phrack.org/issues/51/11.html)" + }, + { + "id": "csi-visual-basic-gui", + "title": "\"I'll create a GUI interface using Visual Basic, see if I can track an IP address.\" What's wrong with this line?", + "description": "From *CSI: NY*, \"Taxi\" (2008). A killer is blogging about the murders in real time, and a detective proposes this plan.", + "type": "single-choice", + "options": [ + { + "text": "A GUI only displays information and can't track anything, and an IP address alone rarely leads to a person", + "isCorrect": true + }, + { + "text": "Visual Basic can't create GUIs", + "isCorrect": false + }, + { + "text": "Nothing: building a custom tracking app is standard forensic practice", + "isCorrect": false + }, + { + "text": "IP addresses are encrypted, so they can't be traced at all", + "isCorrect": false + } + ], + "explanation": "Each word is real, but together they make no sense. A **GUI** (graphical user interface) is the windows and buttons a program shows you, so \"GUI interface\" says \"interface\" twice. Visual Basic really was a popular way to build Windows GUIs, which rules out that distractor. But a front end tracks nothing. It's like offering to paint a dashboard to find a stolen car.\n\nThe tracking part is harder than the show suggests. The blog's host may have logged the IP address the posts came from. That address points to a network connection, not a person: an internet provider's customer, a café's Wi-Fi, a VPN or a proxy. Getting to a name usually means asking the provider for its records, often with a court order. IP addresses aren't secret or encrypted, though, so the last distractor goes too far.", + "references": "[TV Quotes: *CSI: NY*, the Visual Basic line](https://www.tv-quotes.com/shows/csi-ny/quote_25690.html)" + }, + { + "id": "ncis-one-keyboard", + "title": "A hacker floods a lab computer with pop-ups, and two agents fight back by typing on the same keyboard at once. Real or nonsense?", + "description": "From *NCIS*, \"The Bone Yard\" (2004).", + "type": "single-choice", + "options": [ + { + "text": "Nonsense: a keyboard sends one stream of keystrokes, so two typists just scramble each other's commands", + "isCorrect": true + }, + { + "text": "Real: it's pair programming, a standard technique in incident response", + "isCorrect": false + }, + { + "text": "Real: two people typing together can enter commands twice as fast as the attacker", + "isCorrect": false + }, + { + "text": "Real, but only with a split ergonomic keyboard", + "isCorrect": false + } + ], + "explanation": "A keyboard sends key presses one at a time to a single program. When two people type at once, their letters interleave. Two commands turn into one line of gibberish, and neither runs.\n\nPair programming is real, which makes it the tempting distractor. Two people share one computer, but only one types while the other reviews and thinks ahead. Typing speed doesn't matter here anyway, because the attack is software running on the machine, not a person racing the agents.\n\nReal responders start with **containment**. They cut the machine off from the network so the attacker loses access, and they don't wipe it, so the evidence survives. Then they work out how the attacker got in.", + "references": "[Wikipedia: NCIS season 2, \"The Bone Yard\"](https://en.wikipedia.org/wiki/NCIS_season_2)\n\n[NIST SP 800-61 Rev. 2: Computer Security Incident Handling Guide](https://csrc.nist.gov/pubs/sp/800/61/r2/final)" + }, + { + "id": "enhance-blurry-face", + "title": "A detective zooms into a security-camera frame until the suspect's face is a few blocky pixels, then says \"Enhance!\" A sharp face appears. Can software do that?", + "type": "single-choice", + "images": [ + { + "src": "enhance-many-originals.svg", + "alt": "Four different 16 by 16 pixel images: a smiling face, a frowning face, the number 42, and random noise. Averaging each 4 by 4 block of pixels shrinks all four to the same 4 by 4 thumbnail. The caption says \"Enhance!\" would have to guess which image the thumbnail came from.", + "caption": "Diagram: Quizbun.", + "placement": "explanation", + "width": 720, + "height": 470 + } + ], + "options": [ + { + "text": "No: many different sharp faces shrink to the same few pixels, so the detail isn't there to recover", + "isCorrect": true + }, + { + "text": "Yes, if the computer has enough processing power", + "isCorrect": false + }, + { + "text": "Yes: AI upscalers recover the original face exactly", + "isCorrect": false + }, + { + "text": "Yes, but only from uncompressed video, not from JPEGs", + "isCorrect": false + } + ], + "explanation": "When a camera records a face with only a few pixels, each pixel stores one averaged colour for a whole patch of the scene. The diagram shows why that can't be undone. A smile, a frown, the number 42 and random noise all average down to the same 4×4 thumbnail, and nothing in the thumbnail says which one it came from. More computing power doesn't help, because the camera never saved that information.\n\nCompression makes things worse, but uncompressed video doesn't solve the problem. A perfect lossless copy of a tiny face is still a tiny face.\n\nSoftware can sharpen a little when real information is left, such as in a slightly blurred but detailed photo, or across many frames of the same scene. Beyond that, any program that draws a crisp face is **inventing** one. The next Question shows what that looks like.", + "references": "[PULSE paper: Self-Supervised Photo Upsampling via Latent Space Exploration of Generative Models (CVPR 2020)](https://arxiv.org/abs/2003.03808)" + }, + { + "id": "pulse-obama-upsampling", + "title": "In June 2020, someone fed a pixelated photo of Barack Obama into PULSE, a research tool that turns low-resolution faces into sharp ones. What came out?", + "type": "single-choice", + "options": [ + { + "text": "A sharp, realistic face of a white man who wasn't Obama", + "isCorrect": true + }, + { + "text": "A sharp, accurate photo of Obama", + "isCorrect": false + }, + { + "text": "An error, because the tool refused photos of real people", + "isCorrect": false + }, + { + "text": "The same blurry image, because it had nothing to add", + "isCorrect": false + } + ], + "explanation": "PULSE, from researchers at Duke University, doesn't try to recover the real face. It searches the output of a face generator for a sharp face that **shrinks to the same pixels** as the input. As the previous Question showed, many faces match, so PULSE returns the one the generator finds most typical of the faces it learned from.\n\nThe Obama result spread widely and became a standard example of bias in AI. The system's idea of a \"typical\" face came from its training data and design, which pulled the output towards white faces. The researchers themselves said the method creates a plausible face and doesn't recover the person's real identity.\n\nSo an AI-sharpened face can't be evidence. It shows who the model imagined, not who stood in front of the camera.", + "references": "[The Gradient: Lessons from the PULSE model and discussion (2020)](https://thegradient.pub/pulse-lessons/)\n\n[PULSE paper (CVPR 2020)](https://arxiv.org/abs/2003.03808)" + }, + { + "id": "boss-real-tool-real-use", + "title": "Final round: which of these scenes show a real tool doing something it can actually do?", + "type": "multiple-choice", + "options": [ + { + "text": "Trinity finds an open SSH port with an Nmap SYN scan", + "isCorrect": true + }, + { + "text": "Lex browses the park's files in SGI's 3D file manager", + "isCorrect": true + }, + { + "text": "A detective builds a Visual Basic GUI to track an IP address", + "isCorrect": false + }, + { + "text": "Two agents type on one keyboard to beat a hacker", + "isCorrect": false + }, + { + "text": "An investigator says \"Enhance!\" and reads a face from a few pixels", + "isCorrect": false + } + ], + "explanation": "Two scenes pass. Nmap's SYN scan reports open ports exactly as Trinity's screen shows. SGI's fsn was a real IRIX file manager and did what Lex uses it for, showing a directory tree as a 3D landscape.\n\nThe other three fail in different ways. The *CSI: NY* line strings real words into a plan that makes no sense, since a GUI displays information and tracks nothing. The *NCIS* keyboard gets the hardware wrong, because one keyboard sends one stream of keystrokes. \"Enhance!\" gets information wrong. Detail the camera never recorded can only be guessed, and an AI guess is still a guess.\n\nNext time you watch a hacking scene, ask two things. Can you name the real tool on screen? Is it doing the job it was built for?", + "references": "[Nmap: Movies featuring the Nmap security scanner](https://nmap.org/movies/)" + } + ] +} diff --git a/content/quizzes/hollywood-hacking-real-or-nonsense/enhance-many-originals.svg b/content/quizzes/hollywood-hacking-real-or-nonsense/enhance-many-originals.svg new file mode 100644 index 0000000..d454eca --- /dev/null +++ b/content/quizzes/hollywood-hacking-real-or-nonsense/enhance-many-originals.svg @@ -0,0 +1,1103 @@ + + + + + Four different 16×16 images + Smile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Frown + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The number 42 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Random noise + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + average each 4×4 block + + + + + + + + + + + + + + + + + + All four shrink to the same 4×4 thumbnail + "Enhance!" would have to guess which one it came from. + diff --git a/content/quizzes/hollywood-hacking-real-or-nonsense/syn-scan-half-open.svg b/content/quizzes/hollywood-hacking-real-or-nonsense/syn-scan-half-open.svg new file mode 100644 index 0000000..559b464 --- /dev/null +++ b/content/quizzes/hollywood-hacking-real-or-nonsense/syn-scan-half-open.svg @@ -0,0 +1,92 @@ + + + + + + Open port + Nmap + Target + + + + + SYN + + + SYN/ACK + + + RST + SYN/ACK: open + + Closed port + Nmap + Target + + + + + SYN + + + RST + RST: closed + + Filtered port + Nmap + Target + + + + + SYN + + + SYN (retry) + no reply + Silence: filtered + nmap -sS: how a SYN scan tests each port + A normal client answers SYN/ACK with ACK and connects. + The scanner sends RST instead, so the connection never opens. That's a half-open scan. + diff --git a/content/quizzes/how-music-works.json b/content/quizzes/how-music-works.json new file mode 100644 index 0000000..2919edb --- /dev/null +++ b/content/quizzes/how-music-works.json @@ -0,0 +1,712 @@ +{ + "schemaVersion": 1, + "id": "how-music-works", + "title": "How music works: waves, harmonics and why octaves sound the same", + "description": "Why do two notes an octave apart sound like the same note? Why does a clarinet sound hollow? Why is a piano tuned slightly wrong on purpose? Most questions show a waveform, a spectrum or a vibrating string and ask what you'll hear. It's for curious listeners and musicians. You don't need music theory, and the math stops at ratios. You can check several answers by humming or with a free tuner app.", + "language": "en", + "tags": [ + "music", + "acoustics", + "science", + "mathematics" + ], + "questions": [ + { + "id": "octave-same-note", + "title": "Two notes an octave apart sound like the same note, only higher. What do their waves have in common?", + "description": "A3 is 220 Hz and A4 is 440 Hz. The picture shows one pure wave for each note over the same stretch of time.", + "images": [ + { + "src": "octave-waveforms.svg", + "alt": "Two sine waves stacked on the same time axis. The upper row, A3 at 220 Hz, completes 3 cycles. The lower row, A4 at 440 Hz, completes 6 cycles. Four dashed vertical lines mark where each cycle of the 220 Hz wave starts, and the 440 Hz wave also starts a new cycle at every one of them.", + "width": 720, + "height": 300 + }, + { + "src": "octave-harmonic-ladders.svg", + "alt": "Two rows of tick marks on one frequency axis from 0 to 2000 Hz. The top row shows harmonics of A3 at every multiple of 220 Hz. The bottom row shows harmonics of A4 at 440, 880, 1320 and 1760 Hz. A dashed link joins each A4 harmonic to an A3 harmonic at exactly the same frequency, so every A4 tick has a partner.", + "placement": "explanation", + "width": 720, + "height": 290 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The upper wave repeats exactly twice in every cycle of the lower one, so every harmonic of the upper note is also a harmonic of the lower note", + "isCorrect": true + }, + { + "text": "Both waves have the same amplitude, and the ear groups notes by loudness", + "isCorrect": false + }, + { + "text": "Both notes are close to pure sine waves, and all sine waves sound alike", + "isCorrect": false + }, + { + "text": "The upper note's wavelength is twice as long, so the peaks of the two waves line up", + "isCorrect": false + } + ], + "explanation": "With a 2:1 frequency ratio, the two waves fall back into step at the start of every cycle of the lower note. Each dashed line in the first picture starts a fresh cycle of both.\n\nThe harmonics tell the same story. A real A3 has energy at 220, 440, 660, 880 Hz and up. A4 has 440, 880, 1320 Hz and up. Every harmonic of A4 is already in A3, so playing A4 on top of A3 adds no new frequencies, only more energy at existing ones. The two notes blend instead of clashing. That shared structure is a big part of why an octave feels like the same note moved up. It's also why a man and a woman singing \"the same\" tune are usually an octave apart without noticing.\n\nThe wavelength option has it backwards: twice the frequency means half the wavelength. Octave sameness is also partly learned. A 2019 study found that Tsimane' participants in Bolivia, who had heard little Western music, showed little octave equivalence when singing melodies back. US participants showed it clearly.", + "references": "[Wikipedia: Octave](https://en.wikipedia.org/wiki/Octave) · [Current Biology: Universal and non-universal features of musical pitch perception revealed by singing](https://doi.org/10.1016/j.cub.2019.08.020)" + }, + { + "id": "string-mode-frequencies", + "title": "A plucked string vibrates in all of these shapes at once. Shape 1 is at 100 Hz. What frequencies does the string produce?", + "images": [ + { + "src": "string-mode-shapes.svg", + "alt": "Four rows, each a string fixed at both ends and drawn with its vibration envelope. Shape 1 bows out in a single loop and is labeled 100 Hz. Shapes 2, 3 and 4 have two, three and four loops, with still points between them, and are labeled with a question mark in hertz.", + "width": 720, + "height": 350 + } + ], + "type": "single-choice", + "options": [ + { + "text": "100, 200, 300, 400 Hz and upward: every whole-number multiple of 100 Hz", + "isCorrect": true + }, + { + "text": "100, 200, 400, 800 Hz: each shape doubles the frequency of the one before", + "isCorrect": false + }, + { + "text": "100 Hz only, because the other shapes are too weak to hear", + "isCorrect": false + }, + { + "text": "100, 150, 225, 338 Hz: each shape is a fifth above the one before", + "isCorrect": false + } + ], + "explanation": "The ends of the string can't move, so a standing wave fits only if the string holds a whole number of half-wavelengths. Shape 1 holds one half-wavelength, shape 2 holds two, shape 3 holds three. The string's tension and mass per length set how fast waves travel along it, and frequency is that speed divided by wavelength. Half the wavelength gives twice the frequency, a third gives three times, and so on: 100, 200, 300, 400 Hz. That list is the harmonic series, `f_n = n × v / 2L`.\n\nDoubling at every step would give only octaves and skip shape 3, whose three loops give 300 Hz. The upper shapes are quieter than shape 1 but easy to hear. They're what makes a plucked string sound different from a tuning fork at the same pitch.", + "references": "[UNSW Music Acoustics: Strings, standing waves and harmonics](https://newt.phys.unsw.edu.au/jw/strings.html) · [Wikipedia: Harmonic series (music)](https://en.wikipedia.org/wiki/Harmonic_series_%28music%29)" + }, + { + "id": "fret-fifth-length", + "title": "An open guitar string plays a note. How much of the string must vibrate to play the perfect fifth above it, a 3:2 frequency ratio?", + "images": [ + { + "src": "fret-length-ratios.svg", + "alt": "Four horizontal strings from nut to bridge. The open string vibrates along its full length and plays f. Stopped at the 12th fret, half the length vibrates and plays 2f, an octave. At the 7th fret, two-thirds vibrates and plays three-halves f, a fifth. At the 5th fret, three-quarters vibrates and plays four-thirds f, a fourth.", + "placement": "explanation", + "width": 720, + "height": 320 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Two-thirds of its length", + "isCorrect": true + }, + { + "text": "One-third of its length", + "isCorrect": false + }, + { + "text": "Three-quarters of its length", + "isCorrect": false + }, + { + "text": "Half of its length", + "isCorrect": false + } + ], + "explanation": "At fixed tension, a string's frequency is inversely proportional to its vibrating length. To multiply the frequency by 3/2, multiply the length by 2/3. On a guitar, that's the 7th fret. The same rule gives the other simple intervals. Half the length doubles the frequency: the octave, at the 12th fret. Three-quarters of the length gives 4:3: the perfect fourth, at the 5th fret. This is the monochord experiment traditionally credited to the Pythagoreans, and it's why we describe intervals as ratios.\n\nOne-third of the length triples the frequency. That gives the right note name, but an octave too high: an octave plus a fifth above the open string. Modern frets are placed for equal temperament, so the 7th fret actually leaves 0.6674 of the string vibrating, not 0.6667. The difference is too small to see.", + "references": "[UNSW Music Acoustics: Strings, standing waves and harmonics](https://newt.phys.unsw.edu.au/jw/strings.html) · [Wikipedia: Monochord](https://en.wikipedia.org/wiki/Monochord)" + }, + { + "id": "natural-harmonic-midpoint", + "title": "A guitarist rests a fingertip lightly on the exact middle of an open string, without pressing it down, and plucks. What do you hear?", + "images": [ + { + "src": "midpoint-touch-modes.svg", + "alt": "The first four vibration shapes of a string with a dashed vertical line at the midpoint where the finger rests. Shapes f and 3f bulge at the midpoint and are marked damped. Shapes 2f and 4f already have a still point at the midpoint and are marked survives. A caption notes that what survives, 2f, 4f, 6f, is a full harmonic series one octave up.", + "placement": "explanation", + "width": 720, + "height": 380 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A clear, bell-like note one octave above the open string", + "isCorrect": true + }, + { + "text": "The open string's own note, only quieter", + "isCorrect": false + }, + { + "text": "A muted thud with no clear pitch", + "isCorrect": false + }, + { + "text": "A note a fifth above the open string", + "isCorrect": false + } + ], + "explanation": "A light touch doesn't shorten the string the way pressing onto a fret does. It only holds the string still at that one point. Any vibration shape that needs the midpoint to move dies almost at once. Shapes with an odd number of loops (f, 3f, 5f and so on) swing hardest at the middle, so they're damped. Shapes with an even number of loops (2f, 4f, 6f) already have a still point there, so they keep ringing.\n\nWhat's left, 2f, 4f, 6f, is a full harmonic series built on 2f, so you hear a clean note one octave up. The open string's own note can't survive, because its only shape moves most right where the finger is. Touch a third of the way along instead and only 3f, 6f, 9f survive: an octave plus a fifth. Guitarists compare these harmonics on neighboring strings to tune without an electronic tuner.", + "references": "[Wikipedia: String harmonic](https://en.wikipedia.org/wiki/String_harmonic) · [UNSW Music Acoustics: Harmonics](https://newt.phys.unsw.edu.au/jw/harmonics.html)" + }, + { + "id": "pluck-position-tone", + "title": "A guitarist plucks the same open string first near its middle, then right next to the bridge. Which statements about the second pluck are true?", + "images": [ + { + "src": "pluck-position-spectra.svg", + "alt": "Two bar charts of harmonic strength for harmonics 1 to 12. Plucked at the middle: a tall first bar, a small third bar, a tiny fifth bar, and nothing at any even number. Plucked one tenth of the way from the bridge: bars that shrink gradually across harmonics 1 to 9, with the 10th harmonic marked as zero.", + "placement": "explanation", + "width": 720, + "height": 300 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The pitch stays the same", + "isCorrect": true + }, + { + "text": "The upper harmonics get stronger relative to the fundamental, so the note sounds brighter and thinner", + "isCorrect": true + }, + { + "text": "Any harmonic that has a still point exactly at the plucking spot is missing from the sound", + "isCorrect": true + }, + { + "text": "The fundamental frequency rises, because a shorter part of the string was pulled", + "isCorrect": false + }, + { + "text": "Only odd-numbered harmonics sound", + "isCorrect": false + } + ], + "explanation": "Pitch depends on the string's length, tension and mass. Where you pluck changes none of them, so the fundamental stays put. What changes is the mix of harmonics.\n\nPulling the string aside at one point bends it into a triangle. In that triangle, the strength of harmonic n is proportional to `sin(nπβ) / n²`, where β is the plucking point as a fraction of the string's length. A harmonic with a still point right under your finger gets zero, because you can't set a shape moving from the one spot where it doesn't move. That holds wherever you pluck. At the middle it removes every even harmonic, which is where the odd-only option comes from. That option describes the first pluck, not the second.\n\nPlucking one tenth of the way from the bridge removes only the 10th, 20th and so on, and it spreads energy much more evenly into the upper harmonics. That's the bright, nasal tone guitarists get by playing close to the bridge.", + "references": "[UNSW Music Acoustics: Strings, standing waves and harmonics](https://newt.phys.unsw.edu.au/jw/strings.html)" + }, + { + "id": "adjacent-harmonic-intervals", + "title": "Among the first six harmonics of one note, which intervals appear between two neighboring harmonics?", + "description": "Harmonics 1 and 2 are neighbors. Harmonics 2 and 4 are not.", + "images": [ + { + "src": "harmonic-series-intervals.svg", + "alt": "Six vertical marks for harmonics 1 to 6 of a 100 Hz note, from 100 to 600 Hz, placed on a pitch axis so equal ratios take equal space. Brackets between neighbors are labeled octave 2:1, fifth 3:2, fourth 4:3, major third 5:4 and minor third 6:5, each bracket shorter than the one before.", + "placement": "explanation", + "width": 720, + "height": 280 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "Octave", + "isCorrect": true + }, + { + "text": "Perfect fifth", + "isCorrect": true + }, + { + "text": "Perfect fourth", + "isCorrect": true + }, + { + "text": "Major third", + "isCorrect": true + }, + { + "text": "Minor third", + "isCorrect": true + }, + { + "text": "Major second", + "isCorrect": false + }, + { + "text": "Tritone", + "isCorrect": false + } + ], + "explanation": "Neighbors n and n + 1 are in the ratio (n + 1):n. Harmonics 1 and 2 give 2:1, the octave. Then come 3:2 (the fifth), 4:3 (the fourth), 5:4 (the major third) and 6:5 (the minor third). The core intervals of Western harmony all live inside the sound of a single string or pipe, which is one reason those ratios got their names first.\n\nThe steps shrink as you climb. A major second first shows up between harmonics 8 and 9, as 9:8, past the first six. A tritone needs a ratio like 7:5, which links harmonics 5 and 7. Those are two steps apart, not neighbors.", + "references": "[Wikipedia: Harmonic series (music)](https://en.wikipedia.org/wiki/Harmonic_series_%28music%29)" + }, + { + "id": "clarinet-spectrum", + "title": "One spectrum is a clarinet in its low register, the other is a flute. Which one is the clarinet?", + "description": "Each bar is one harmonic of the same 147 Hz note. Bar height is its strength.", + "images": [ + { + "src": "two-spectra-x-y.svg", + "alt": "Two bar charts of harmonics 1 to 10 for the same 147 Hz note. Instrument X: every harmonic is present, and the bars shrink smoothly from left to right. Instrument Y: harmonics 1, 3, 5, 7 and 9 are tall, while harmonics 2, 4, 6, 8 and 10 are barely visible.", + "width": 720, + "height": 290 + }, + { + "src": "pipe-modes-open-vs-closed.svg", + "alt": "Air motion inside two pipes for their three lowest resonances. The pipe open at both ends, like a flute, has maximum motion at both ends and resonates at f, 2f and 3f. The pipe closed at one end, like a clarinet, has no motion at the closed end and maximum motion at the open end, and resonates at f, 3f and 5f.", + "placement": "explanation", + "width": 720, + "height": 360 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Instrument Y, whose even-numbered harmonics are almost missing", + "isCorrect": true + }, + { + "text": "Instrument X, whose harmonics fade smoothly with no gaps", + "isCorrect": false + }, + { + "text": "Neither: a clarinet and a flute playing the same note have the same spectrum", + "isCorrect": false + }, + { + "text": "A spectrum can't tell them apart; only the waveform's shape identifies an instrument", + "isCorrect": false + } + ], + "explanation": "A flute is open at both ends, so air moves freely in and out at each end. The standing waves that fit hold a whole number of half-wavelengths, which gives the full series f, 2f, 3f.\n\nA clarinet's reed and mouthpiece close off its top end, so the air there can't swing back and forth, while the bell end is open. Between a still end and a free end, only an odd number of quarter-wavelengths fits, which gives f, 3f, 5f. In the low register the even harmonics are weak but not gone. That's the gap-toothed pattern in instrument Y, and it's where the clarinet's hollow sound comes from. Higher up the range, the even harmonics fill back in. The quarter-wavelength fit also explains why a clarinet plays about an octave lower than a flute of similar length.\n\nBore shape matters too. The oboe and saxophone also use reeds, but their cone-shaped bores bring back the full harmonic series.", + "references": "[UNSW Music Acoustics: Open vs closed pipes](https://newt.phys.unsw.edu.au/jw/pipes.html) · [UNSW Music Acoustics: Clarinet acoustics](https://newt.phys.unsw.edu.au/jw/clarinetacoustics.html)" + }, + { + "id": "closed-pipe-next-resonance", + "title": "A pipe closed at one end resonates lowest at 100 Hz. What is the next resonance frequency above that, in hertz?", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 300 + ] + }, + "explanation": "The air can't move at the closed end and moves most at the open end. The shortest wave that fits puts a quarter of a wavelength in the pipe, and that's the 100 Hz resonance. To fit another one, the wave has to go from still to free again, which adds half a wavelength. Three quarter-wavelengths in the same pipe means three times the frequency: 300 Hz. After that come 500 Hz, 700 Hz and so on.\n\n200 Hz is the tempting answer. It would put two quarter-wavelengths, half a wavelength, in the pipe, and that needs the same condition at both ends: still at both or free at both. A pipe with one closed end can't do that. The stopped pipes of a pan flute follow the same rule.", + "references": "[UNSW Music Acoustics: Open vs closed pipes](https://newt.phys.unsw.edu.au/jw/pipes.html)" + }, + { + "id": "clarinet-overblow-twelfth", + "title": "Overblow a flute and its note jumps up an octave. When a clarinetist presses the register key, how far up does the note jump?", + "type": "single-choice", + "options": [ + { + "text": "An octave plus a fifth, the interval called a twelfth", + "isCorrect": true + }, + { + "text": "An octave, the same as the flute", + "isCorrect": false + }, + { + "text": "Two octaves", + "isCorrect": false + }, + { + "text": "A perfect fifth", + "isCorrect": false + } + ], + "explanation": "Overblowing, or opening a small register vent, makes the air column lock onto a higher resonance instead of its lowest one. A flute is a pipe open at both ends, so its next resonance is 2f, one octave up. A clarinet acts like a pipe closed at one end and has no strong 2f resonance. Its next one is 3f. A 3:1 ratio is an octave (2:1) plus a fifth (3:2), the interval called a twelfth.\n\nThat's why the clarinet's register key isn't called an octave key. It's also why clarinet fingerings don't repeat from one register to the next the way flute and saxophone fingerings do. The same fingering one register up plays a twelfth higher, so clarinetists learn two different sets. The saxophone also uses a single reed, but its cone-shaped bore overblows at the octave.", + "references": "[UNSW Music Acoustics: Clarinet acoustics](https://newt.phys.unsw.edu.au/jw/clarinetacoustics.html) · [Wikipedia: Register key](https://en.wikipedia.org/wiki/Register_key)" + }, + { + "id": "phase-changes-waveform", + "title": "Waves A and B contain the same six harmonics at the same strengths. Only the timing of each harmonic differs. How do they sound as steady tones?", + "images": [ + { + "src": "same-harmonics-two-phases.svg", + "alt": "Two waveforms, each shown for two cycles. Wave A is a steep sawtooth-like ramp with a sharp drop once per cycle. Wave B has the same repeat length but a completely different, lumpy shape with small ripples and one deep dip per cycle.", + "width": 720, + "height": 300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The same, or so close that most listeners can't tell them apart", + "isCorrect": true + }, + { + "text": "Very different, because the ear hears the shape of the wave", + "isCorrect": false + }, + { + "text": "B sounds higher, because its wave has more wiggles", + "isCorrect": false + }, + { + "text": "A sounds much harsher, because its wave has sharper corners", + "isCorrect": false + } + ], + "explanation": "The inner ear works like a bank of filters, each tuned to a narrow band of frequencies. For a steady tone, it mostly reports how much energy lands in each band, and A and B match there: same harmonics, same strengths. Shifting when each harmonic peaks (its phase) changes the waveform's shape a lot, but the ear throws most of that timing away. Helmholtz built on Ohm's work to state this in the 19th century, and it's known as Ohm's acoustic law.\n\nThe law has limits. With a low fundamental and many high harmonics crowded into one filter, careful listeners can sometimes hear a phase change as a slight change in roughness. The takeaway still holds: judge timbre from the spectrum, not from the outline on an oscilloscope. Pitch doesn't change either, since both waves repeat at the same rate, twice in the window shown.", + "references": "[Wikipedia: Ohm's acoustic law](https://en.wikipedia.org/wiki/Ohm%27s_acoustic_law) · [Wikipedia: Timbre](https://en.wikipedia.org/wiki/Timbre)" + }, + { + "id": "helium-voice", + "title": "After breathing in helium, a person's voice sounds squeaky and high. What actually changed?", + "images": [ + { + "src": "helium-formant-shift.svg", + "alt": "Two spectra of the same sung vowel at 150 Hz, from 0 to 4 kHz. In both, harmonic lines are evenly spaced every 150 Hz. In air, a dashed resonance curve peaks near 600, 1500 and 2600 Hz. In helium, the peaks sit higher, near 1000, 2500 and 3700 Hz, while the harmonic lines stay at the same positions.", + "placement": "explanation", + "width": 720, + "height": 320 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The resonances of the throat and mouth moved up, while the vocal folds kept vibrating at almost the same rate", + "isCorrect": true + }, + { + "text": "The vocal folds vibrate faster, because helium is lighter than air", + "isCorrect": false + }, + { + "text": "Helium carries high frequencies better than air, so the listener hears less bass", + "isCorrect": false + }, + { + "text": "Every harmonic rose by the same factor, like a sped-up recording", + "isCorrect": false + } + ], + "explanation": "A voice has two separate parts. The vocal folds set the pitch. They open and close at the fundamental frequency and make a buzz full of harmonics. The throat, mouth and nose then act as resonators that boost some harmonics and weaken others. Those resonance peaks, called formants, are what make \"ah\" sound different from \"ee\".\n\nThe resonant frequencies of an air space scale with the speed of sound, and sound travels nearly three times faster in helium than in air, so the formants jump up. The vocal folds are tissue driven by muscle tension, so they keep buzzing at about the same rate, and the harmonics stay where they were. You get the same pitch with the treble boosted, which the ear hears as small and squeaky. In practice the shift is smaller than the full factor, because the helium mixes with air in the lungs. The sound reaches the listener through ordinary room air, so the air in between plays no part.\n\nA sped-up recording raises the pitch and the formants together. That's why it sounds like a chipmunk and not like helium.", + "references": "[Wikipedia: Formant](https://en.wikipedia.org/wiki/Formant)" + }, + { + "id": "which-sounds-harmonic", + "title": "Which of these sounds have overtones at, or very close to, whole-number multiples of their lowest frequency?", + "type": "multiple-choice", + "options": [ + { + "text": "A bowed violin note", + "isCorrect": true + }, + { + "text": "A sung vowel", + "isCorrect": true + }, + { + "text": "A clarinet note", + "isCorrect": true + }, + { + "text": "A crash cymbal", + "isCorrect": false + }, + { + "text": "A church bell", + "isCorrect": false + }, + { + "text": "A snare drum hit", + "isCorrect": false + } + ], + "explanation": "Harmonic overtones come from sustained vibration of a string or an air column: a string driven by a bow, air in a tube driven by a reed, the vocal tract driven by the vocal folds. In a bowed or blown note, the driving force locks every overtone onto exact multiples of the fundamental. The clarinet counts even with its weak even harmonics, because the ones it has still sit on the whole-number grid.\n\nPlates, shells and membranes vibrate in two dimensions, and their natural modes fall at irregular ratios. A cymbal has so many irregular modes that a crash sounds like noise. A snare drum's head, rattled by its wires, gives a burst with no steady pitch. A church bell is the interesting case. Bell founders shape the bell so several of its partials land on musical intervals, but the result still isn't a harmonic series: one tuned partial, the tierce, sits a minor third above the prime. The strike note you hear as the bell's pitch doesn't match any single partial either. Your brain infers it.", + "references": "[Wikipedia: Inharmonicity](https://en.wikipedia.org/wiki/Inharmonicity) · [Wikipedia: Strike tone](https://en.wikipedia.org/wiki/Strike_tone)" + }, + { + "id": "drum-inharmonic", + "title": "Why does a struck drumhead give a vaguer pitch than a plucked string?", + "images": [ + { + "src": "membrane-vs-string-partials.svg", + "alt": "Two rows of overtones plotted as multiples of the lowest frequency, from 0 to 4 times, with dashed grid lines at 1, 2, 3 and 4. The string's overtones sit exactly on the grid lines. The ideal drumhead's overtones fall at 1, 1.59, 2.14, 2.30, 2.65, 2.92, 3.16, 3.50, 3.60 and 3.65, mostly between grid lines.", + "placement": "explanation", + "width": 720, + "height": 270 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A drumhead's overtones fall at irregular multiples of its lowest frequency, so no single pitch explains them all", + "isCorrect": true + }, + { + "text": "A drumhead has no overtones, and a tone without overtones has no pitch", + "isCorrect": false + }, + { + "text": "Drum sounds are too short for the ear to measure a pitch", + "isCorrect": false + }, + { + "text": "A drumhead's lowest mode is below the range of human hearing", + "isCorrect": false + } + ], + "explanation": "To hear a pitch, the ear looks for one fundamental whose harmonics explain the frequencies it hears. A string makes that easy, because every overtone is a multiple of one frequency. An ideal circular membrane vibrates at 1, 1.59, 2.14, 2.30, 2.65 and 2.92 times its lowest frequency, and no harmonic series fits that set. You get a thump with a rough pitch region instead of a note.\n\nThe other options fail simple checks. A tuning fork has almost no overtones and a perfectly clear pitch. A timpani note rings for seconds. Timpani also show how to give a drum a pitch. The air in the kettle and around the head loads the membrane, so several of its modes shift close to the ratios 2 : 3 : 4. That's close enough to a harmonic series for timpani to play definite notes, tuned to the key of the piece.", + "references": "[Wikipedia: Vibrations of a circular membrane](https://en.wikipedia.org/wiki/Vibrations_of_a_circular_membrane) · [Wikipedia: Timpani](https://en.wikipedia.org/wiki/Timpani)" + }, + { + "id": "beats-what-you-hear", + "title": "Two flutes hold steady notes at 440 Hz and 442 Hz. What do you hear?", + "images": [ + { + "src": "beat-envelope.svg", + "alt": "Three waveforms, slowed down for display. Tone 1 has 20 cycles in the window and tone 2 has 22. Their sum has a dashed outline that swells to double height, shrinks to nothing, and swells again, twice across the window. A caption reads: the sum swells and fades 22 minus 20 equals 2 times per window.", + "placement": "explanation", + "width": 720, + "height": 350 + } + ], + "type": "single-choice", + "options": [ + { + "text": "One tone near 441 Hz that swells and fades twice per second", + "isCorrect": true + }, + { + "text": "Two separate notes, one slightly higher than the other", + "isCorrect": false + }, + { + "text": "The two notes plus a steady low hum at 2 Hz", + "isCorrect": false + }, + { + "text": "One steady tone at 441 Hz with constant loudness", + "isCorrect": false + } + ], + "explanation": "Frequencies this close excite the same filter in the inner ear, so you hear one tone at their average instead of two notes. Over time, the two waves drift in and out of step. When their peaks line up they add up, and half a beat later a peak meets a trough and they cancel. That happens as many times per second as the frequencies differ: 442 − 440 = 2 beats per second.\n\nA 2 Hz \"tone\" would be far below the range of hearing, so there's no low hum, only the loudness rising and falling. Move the two notes further apart and the beats speed up into a rough buzz. Eventually the sound splits into two distinct notes.", + "references": "[Wikipedia: Beat (acoustics)](https://en.wikipedia.org/wiki/Beat_%28acoustics%29)" + }, + { + "id": "tuning-by-beats", + "title": "You tune a guitar string against a 440 Hz reference and hear 3 beats per second. You tighten the string a little, and the beats speed up to 5 per second. What was the string's frequency before you tightened it, in hertz?", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 443 + ] + }, + "explanation": "Three beats per second means the string was 3 Hz away from 440, so it was either 437 or 443 Hz. Beats alone never tell you which side you're on. Tightening a string always raises its frequency. From 437 Hz, tightening would move it toward 440 and the beats would slow down. They sped up, so the string moved away from 440 and must have started above it: 443 Hz. It's now about 445 Hz.\n\nTo fix it, loosen the string until the beats slow down and stop. If they start again, you've gone too far. Tuners used this nudge-and-listen method long before electronic tuners existed.", + "references": "[Wikipedia: Beat (acoustics)](https://en.wikipedia.org/wiki/Beat_%28acoustics%29)" + }, + { + "id": "fifth-smooth-tritone-rough", + "title": "Played on a violin, a perfect fifth sounds smooth and a tritone sounds rough and tense. What is one physical reason?", + "images": [ + { + "src": "fifth-vs-tritone-harmonics.svg", + "alt": "Two panels comparing harmonics of a 200 Hz note with an upper note. Perfect fifth at 300 Hz: upper-note harmonics at 600 and 1200 Hz join lower-note harmonics with green exact-match links. Tritone at 283 Hz: red links join near misses with beat rates of 34, 49, 69 and 14 per second.", + "placement": "explanation", + "width": 720, + "height": 370 + } + ], + "type": "single-choice", + "options": [ + { + "text": "In a fifth, many harmonics of the two notes coincide exactly. In a tritone, many land close together without matching, and those pairs beat rapidly", + "isCorrect": true + }, + { + "text": "The tritone's two notes are farther apart than a fifth's, and wide intervals sound rough", + "isCorrect": false + }, + { + "text": "The upper note of a tritone is louder than the lower one", + "isCorrect": false + }, + { + "text": "A tritone produces frequencies outside the range of human hearing", + "isCorrect": false + } + ], + "explanation": "The picture uses a 200 Hz note. Add a pure fifth at 300 Hz, and its harmonics at 600 and 1200 Hz land exactly on harmonics of the lower note, while the rest stay well clear. Add an equal-tempered tritone at about 283 Hz, and several of its harmonics miss a lower-note harmonic by 14 to 69 Hz. Those near misses beat tens of times per second, which the ear hears as roughness. It's the slow beating from the two-flutes question, sped up past the point where you can count it.\n\nHermann von Helmholtz proposed this explanation in 1863. In 1965, experiments by Plomp and Levelt found that roughness peaks when two tones fall close together inside one of the ear's frequency filters. Roughness isn't the whole story of consonance, though. Which intervals feel tense also depends on musical training and style, and the tritone is right at home in blues and jazz. The width option is backwards: a tritone is narrower than a fifth.", + "references": "[Wikipedia: Consonance and dissonance](https://en.wikipedia.org/wiki/Consonance_and_dissonance)" + }, + { + "id": "twelve-fifths-comma", + "title": "Stack twelve pure 3:2 fifths up from C, and you'd expect to land on C again, seven octaves higher. Instead you overshoot. By how much, and what do modern keyboards do about it?", + "images": [ + { + "src": "spiral-of-fifths.svg", + "alt": "Twelve pure fifths stacked from C and plotted on the octave circle. Angle shows each note's pitch within the octave and distance from the center counts the fifths stacked. Dashed spokes mark the twelve equally spaced notes. The points spiral outward through G, D, A, E, B, F sharp, C sharp, G sharp, D sharp, A sharp and E sharp. Step 12, B sharp, lands slightly clockwise of C's spoke instead of on it, and a dashed line with a question mark joins it back to C.", + "width": 720, + "height": 500 + } + ], + "type": "single-choice", + "options": [ + { + "text": "By about 23.5 cents, roughly a quarter of a semitone. Equal temperament narrows every fifth by about 2 cents so the circle closes", + "isCorrect": true + }, + { + "text": "By about 23.5 cents. Keyboards leave one fifth 23.5 cents narrow and avoid the keys that use it", + "isCorrect": false + }, + { + "text": "By about 2 cents, which is too small to hear, so nothing is done about it", + "isCorrect": false + }, + { + "text": "There is no overshoot; the stack lands exactly on C on a properly tuned instrument", + "isCorrect": false + } + ], + "explanation": "Twelve fifths multiply the frequency by (3/2)¹² ≈ 129.75. Seven octaves multiply it by 2⁷ = 128. The mismatch, 129.75 / 128 ≈ 1.0136, is the Pythagorean comma: about 23.5 cents, where 100 cents is one equal-tempered semitone. In the picture, step 12, B♯, lands just past the C it was meant to return to. There's no way around the overshoot. A power of 3/2 has an odd number on top and a power of 2 has an even one, so no stack of pure fifths can ever equal a stack of octaves.\n\nOlder Pythagorean tunings pushed the whole comma into one fifth, the \"wolf\" fifth, which sounded so bad that music steered clear of it. That's the second option, and it describes history, not modern keyboards. Equal temperament spreads the comma across all twelve fifths. Each one is 700 cents instead of about 702, about 2 cents short, which few people can hear, and every key becomes usable.", + "references": "[Wikipedia: Pythagorean comma](https://en.wikipedia.org/wiki/Pythagorean_comma) · [Wikipedia: Equal temperament](https://en.wikipedia.org/wiki/Equal_temperament)" + }, + { + "id": "equal-temperament-semitone", + "title": "On an equal-tempered keyboard, how are the frequencies of the twelve notes in one octave spaced?", + "images": [ + { + "src": "semitones-log-spacing.svg", + "alt": "The thirteen equal-tempered notes from A at 220 Hz to A at 440 Hz, marked on a plain hertz scale. The marks spread further apart toward the top. The first gap, A to A sharp, is labeled 13.1 Hz, and the last gap, G sharp to A, is labeled 24.7 Hz.", + "placement": "explanation", + "width": 720, + "height": 250 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Each note's frequency is the previous one's multiplied by the twelfth root of 2, about 1.0595", + "isCorrect": true + }, + { + "text": "Each note is the previous one plus one-twelfth of the octave's width in hertz", + "isCorrect": false + }, + { + "text": "Each note's frequency is the previous one's multiplied by 13/12", + "isCorrect": false + }, + { + "text": "The notes follow the harmonic series, so the steps get smaller toward the top", + "isCorrect": false + } + ], + "explanation": "Pitch works in ratios, not differences. 220 → 440 Hz and 440 → 880 Hz both sound like one octave, even though one gap is 220 Hz wide and the other is 440 Hz. So steps that sound equal need equal ratios. Twelve identical steps must multiply to 2, so each step is 2^(1/12) ≈ 1.0595, a rise of about 6%.\n\nMeasured in hertz, the steps grow as you climb. A3 to A♯3 is 13.1 Hz, and G♯4 to A4 is 24.7 Hz. With equal steps in hertz, the low semitones would sound huge and the high ones tiny. Multiplying by 13/12 twelve times gives about 2.61, which overshoots the octave badly.", + "references": "[Wikipedia: Equal temperament](https://en.wikipedia.org/wiki/Equal_temperament)" + }, + { + "id": "equal-temperament-tradeoffs", + "title": "Which statements about equal temperament, the tuning of nearly every modern piano and fretted guitar, are true?", + "type": "multiple-choice", + "options": [ + { + "text": "A piece sounds the same in every key, only higher or lower", + "isCorrect": true + }, + { + "text": "Its fifths are slightly narrower than a pure 3:2 fifth", + "isCorrect": true + }, + { + "text": "Its major thirds are noticeably wider than a pure 5:4 third", + "isCorrect": true + }, + { + "text": "All of its intervals except the octave are pure whole-number ratios", + "isCorrect": false + }, + { + "text": "It removes the Pythagorean comma from the physics of sound", + "isCorrect": false + } + ], + "explanation": "Equal temperament accepts small errors everywhere to avoid large errors anywhere. Every semitone is the same ratio, so a piece can move to any key and keep its character. The cost falls on the intervals. Fifths are 700 cents against a pure 702, about 2 cents narrow and nearly inaudible. Fourths are about 2 cents wide. Major thirds are 400 cents against a pure 386, almost 14 cents wide. You can hear that as a slow shimmer of beats when you hold a major chord on a piano.\n\nOnly the octave is pure, the reverse of what the fourth statement claims. And the comma hasn't gone anywhere. It's been cut into twelve slices of about 2 cents, one per fifth. Singers and string players can adjust each note as they go, and they often drift toward purer thirds. A keyboard player can't.", + "references": "[Wikipedia: Equal temperament](https://en.wikipedia.org/wiki/Equal_temperament)" + }, + { + "id": "missing-fundamental-phone", + "title": "A phone's tiny speaker can't produce 60 Hz, yet you still hear a bass line play a 60 Hz note. Why?", + "type": "single-choice", + "options": [ + { + "text": "The speaker still plays the note's harmonics at 120, 180, 240 Hz and up, and the brain infers the missing 60 Hz fundamental from them", + "isCorrect": true + }, + { + "text": "Your ear canal resonates at 60 Hz and supplies the missing note", + "isCorrect": false + }, + { + "text": "The phone's case vibrates at 60 Hz and carries the note through your hand", + "isCorrect": false + }, + { + "text": "Nothing is heard at that pitch; you remember the bass from hearing the song on better speakers", + "isCorrect": false + } + ], + "explanation": "The brain doesn't read pitch off the lowest frequency present. It looks for a pattern. 120, 180 and 240 Hz are all multiples of 60 Hz, and 60 Hz is the largest frequency they're all multiples of. So you hear a 60 Hz pitch even though nothing at 60 Hz reaches your ear. This is called the missing fundamental, or virtual pitch. Audio processing uses it on purpose: bass-enhancement effects add extra harmonics of low notes a small speaker can't play, so it sounds fuller than it is.\n\nThe effect brings back the pitch, not the physical thump. The ear canal resonates around 3 kHz, far from 60 Hz. And people hear the missing fundamental even in sounds they've never heard before, so memory can't explain it.", + "references": "[Wikipedia: Missing fundamental](https://en.wikipedia.org/wiki/Missing_fundamental)" + }, + { + "id": "missing-fundamental-pitch", + "title": "This sound contains only the frequencies shown. What pitch do listeners hear, in hertz?", + "images": [ + { + "src": "missing-fundamental-spectrum.svg", + "alt": "A spectrum from 0 to 1200 Hz with four bars, at 400, 600, 800 and 1000 Hz, getting slightly shorter from left to right. There is nothing below 400 Hz.", + "width": 720, + "height": 270 + } + ], + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 200 + ] + }, + "explanation": "400, 600, 800 and 1000 Hz are consecutive multiples of 200 Hz. They're harmonics 2 to 5 of a 200 Hz note with its first harmonic removed. The auditory system reports the fundamental that best explains the whole pattern, so listeners match the pitch to a 200 Hz tone, an octave below the lowest frequency actually present.\n\n400 Hz is the tempting answer. But a 400 Hz fundamental would have harmonics at 800 and 1200 Hz, leaving 600 and 1000 Hz unexplained. The spacing gives it away: each component is 200 Hz from the next.", + "references": "[Wikipedia: Missing fundamental](https://en.wikipedia.org/wiki/Missing_fundamental)" + }, + { + "id": "piano-stretch-tuning", + "title": "A piano tuner deliberately tunes the highest notes sharp and the lowest notes flat compared with exact equal temperament. Why?", + "images": [ + { + "src": "piano-stretch-curve.svg", + "alt": "Schematic curve of tuning deviation in cents across the piano keyboard from A0 to C8. It sits near zero through the middle, around C4 and A4, falls to about minus 25 cents at the bass end, and rises to about plus 30 cents at the treble end. Labels read bass tuned flat and treble tuned sharp.", + "placement": "explanation", + "width": 720, + "height": 320 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Stiff piano strings have overtones slightly sharper than whole-number multiples, so octaves are widened to match them and sound in tune", + "isCorrect": true + }, + { + "text": "Treble strings go flat as the piano warms up, so the tuner sets them sharp in advance", + "isCorrect": false + }, + { + "text": "Equal temperament only works in the middle of the keyboard, so the outer octaves use just intonation", + "isCorrect": false + }, + { + "text": "It is purely a matter of taste: a brighter treble, with no physical cause", + "isCorrect": false + } + ], + "explanation": "An ideal string is perfectly flexible. A real piano string is steel wire, and its stiffness adds a restoring force that matters most for the short, tightly curved upper modes. Each overtone ends up slightly sharp of its whole-number position, and the error grows with harmonic number. This is called inharmonicity.\n\nA tuner sets each octave by listening for beats between the upper note and the second partial of the lower note. Because that partial is sharp, a beat-free octave comes out slightly wider than exactly 2:1. The stretch adds up octave by octave, so near the ends of the keyboard the notes sit tens of cents off strict equal temperament. This pattern is called the Railsback curve, after O. L. Railsback, who measured it in 1938. Tune the \"correct\" way and the octaves would beat, and the top of the piano would sound flat against its own bass.\n\nSo a well-tuned piano is out of tune on purpose, twice over. Equal temperament bends every interval except the octave, and stretch tuning bends the octave too. Both choices make the instrument sound more in tune.", + "references": "[Wikipedia: Stretched tuning](https://en.wikipedia.org/wiki/Stretched_tuning) · [Wikipedia: Inharmonicity](https://en.wikipedia.org/wiki/Inharmonicity)" + } + ] +} diff --git a/content/quizzes/how-music-works/beat-envelope.svg b/content/quizzes/how-music-works/beat-envelope.svg new file mode 100644 index 0000000..6f7ea79 --- /dev/null +++ b/content/quizzes/how-music-works/beat-envelope.svg @@ -0,0 +1,59 @@ + + + + + two close tones and their sum, slowed down to 20 and 22 cycles + tone 1 + + tone 2 + + sum + + + + the sum swells and fades 22 − 20 = 2 times per window + diff --git a/content/quizzes/how-music-works/fifth-vs-tritone-harmonics.svg b/content/quizzes/how-music-works/fifth-vs-tritone-harmonics.svg new file mode 100644 index 0000000..537c897 --- /dev/null +++ b/content/quizzes/how-music-works/fifth-vs-tritone-harmonics.svg @@ -0,0 +1,106 @@ + + + + + harmonics of a 200 Hz note against the note played with it + perfect fifth (300 Hz) + upper note + 200 Hz note + + + + + + + + + + + + + + + + tritone (283 Hz) + upper note + 200 Hz note + + + + + + + + + + + + 34 + + + 49 + + + 69 + + + 14 + + + + 0 Hz + + 400 Hz + + 800 Hz + + 1200 Hz + + 1600 Hz + green: exact match + red: near miss, beats per second + diff --git a/content/quizzes/how-music-works/fret-length-ratios.svg b/content/quizzes/how-music-works/fret-length-ratios.svg new file mode 100644 index 0000000..8715b11 --- /dev/null +++ b/content/quizzes/how-music-works/fret-length-ratios.svg @@ -0,0 +1,80 @@ + + + + + vibrating length sets the frequency: half the length, twice the frequency + open string + + + + + length 1 + f + 12th fret + + + + + length 1/2 + 2f · octave + 7th fret + + + + + length 2/3 + 3/2 f · fifth + 5th fret + + + + + length 3/4 + 4/3 f · fourth + nut + bridge + diff --git a/content/quizzes/how-music-works/harmonic-series-intervals.svg b/content/quizzes/how-music-works/harmonic-series-intervals.svg new file mode 100644 index 0000000..96dd73a --- /dev/null +++ b/content/quizzes/how-music-works/harmonic-series-intervals.svg @@ -0,0 +1,79 @@ + + + + + the first six harmonics of a 100 Hz note, on a pitch (log) axis + + 1 + 100 Hz + + 2 + 200 Hz + + 3 + 300 Hz + + 4 + 400 Hz + + 5 + 500 Hz + + 6 + 600 Hz + + octave 2:1 + + fifth 3:2 + + fourth 4:3 + + major third 5:4 + + minor third 6:5 + each step up the series is a smaller interval than the one before + diff --git a/content/quizzes/how-music-works/helium-formant-shift.svg b/content/quizzes/how-music-works/helium-formant-shift.svg new file mode 100644 index 0000000..28ac0e4 --- /dev/null +++ b/content/quizzes/how-music-works/helium-formant-shift.svg @@ -0,0 +1,121 @@ + + + + + one vowel sung at 150 Hz: the harmonics stay put, the resonance peaks move up + in air + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0k + 1k + 2k + 3k + 4k + frequency (Hz) + in helium + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0k + 1k + 2k + 3k + 4k + frequency (Hz) + solid lines: harmonics of the voice · dashed: vocal-tract resonances + diff --git a/content/quizzes/how-music-works/membrane-vs-string-partials.svg b/content/quizzes/how-music-works/membrane-vs-string-partials.svg new file mode 100644 index 0000000..9f2d027 --- /dev/null +++ b/content/quizzes/how-music-works/membrane-vs-string-partials.svg @@ -0,0 +1,84 @@ + + + + + overtones as multiples of the lowest frequency + + + + + string + + + + + + ideal drumhead + + + + + + + + + + + + + + 0× + + 1× + + 2× + + 3× + + 4× + string overtones sit on the dashed whole-number grid; the drumhead's do not + diff --git a/content/quizzes/how-music-works/midpoint-touch-modes.svg b/content/quizzes/how-music-works/midpoint-touch-modes.svg new file mode 100644 index 0000000..6e50f3b --- /dev/null +++ b/content/quizzes/how-music-works/midpoint-touch-modes.svg @@ -0,0 +1,77 @@ + + + + + a light fingertip at the midpoint forces a still point there + + + + + f + damped + + + + + 2f + survives + + + + + 3f + damped + + + + + 4f + survives + + finger + what survives, 2f, 4f, 6f…, is a full harmonic series one octave up + diff --git a/content/quizzes/how-music-works/missing-fundamental-spectrum.svg b/content/quizzes/how-music-works/missing-fundamental-spectrum.svg new file mode 100644 index 0000000..0b8f48a --- /dev/null +++ b/content/quizzes/how-music-works/missing-fundamental-spectrum.svg @@ -0,0 +1,70 @@ + + + + + the complete spectrum of a sound + + + + + + + 0 + + 200 + + 400 + + 600 + + 800 + + 1000 + + 1200 + frequency (Hz) + diff --git a/content/quizzes/how-music-works/octave-harmonic-ladders.svg b/content/quizzes/how-music-works/octave-harmonic-ladders.svg new file mode 100644 index 0000000..4eb518f --- /dev/null +++ b/content/quizzes/how-music-works/octave-harmonic-ladders.svg @@ -0,0 +1,83 @@ + + + + + harmonics of each note on one frequency axis + A3 · 220 Hz + + + + + + + + + + + A4 · 440 Hz + + + + + + + + + + + + 0 Hz + + 500 Hz + + 1000 Hz + + 1500 Hz + + 2000 Hz + every harmonic of A4 (dashed links) is also a harmonic of A3 + diff --git a/content/quizzes/how-music-works/octave-waveforms.svg b/content/quizzes/how-music-works/octave-waveforms.svg new file mode 100644 index 0000000..95c5164 --- /dev/null +++ b/content/quizzes/how-music-works/octave-waveforms.svg @@ -0,0 +1,61 @@ + + + + + two notes an octave apart, drawn over the same stretch of time + A3 · 220 Hz + + + A4 · 440 Hz + + + + + + + dashed lines: the start of each cycle of the lower note + diff --git a/content/quizzes/how-music-works/piano-stretch-curve.svg b/content/quizzes/how-music-works/piano-stretch-curve.svg new file mode 100644 index 0000000..8d82d4d --- /dev/null +++ b/content/quizzes/how-music-works/piano-stretch-curve.svg @@ -0,0 +1,76 @@ + + + + + how far a tuned piano sits from strict equal temperament (typical shape) + + -30 + + -15 + + 0 + + +15 + + +30 + + + + + A0 + + C4 + + A4 + + C8 + + cents + treble tuned sharp + bass tuned flat + simplified shape: the real curve depends on how stiff each piano's strings are + diff --git a/content/quizzes/how-music-works/pipe-modes-open-vs-closed.svg b/content/quizzes/how-music-works/pipe-modes-open-vs-closed.svg new file mode 100644 index 0000000..3af1ce3 --- /dev/null +++ b/content/quizzes/how-music-works/pipe-modes-open-vs-closed.svg @@ -0,0 +1,80 @@ + + + + + air movement inside the pipe for its lowest three resonances + open at both ends (flute) + + + + f + + + + 2f + + + + 3f + closed at one end (clarinet) + + + + + f + + + + + 3f + + + + + 5f + air can't move at a closed end, so only odd numbers of quarter-wavelengths fit + diff --git a/content/quizzes/how-music-works/pluck-position-spectra.svg b/content/quizzes/how-music-works/pluck-position-spectra.svg new file mode 100644 index 0000000..6b78184 --- /dev/null +++ b/content/quizzes/how-music-works/pluck-position-spectra.svg @@ -0,0 +1,98 @@ + + + + + same string, same pitch, different pluck point (relative strength) + plucked at the middle + + + 1 + 2 + + 3 + 4 + + 5 + 6 + + 7 + 8 + + 9 + 10 + + 11 + 12 + harmonic number + plucked 1/10 of the way from the bridge + + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 + 10 + + 11 + + 12 + harmonic number + 10th: 0 + diff --git a/content/quizzes/how-music-works/same-harmonics-two-phases.svg b/content/quizzes/how-music-works/same-harmonics-two-phases.svg new file mode 100644 index 0000000..32729c1 --- /dev/null +++ b/content/quizzes/how-music-works/same-harmonics-two-phases.svg @@ -0,0 +1,57 @@ + + + + + two waves built from the same six harmonics at the same strengths + wave A + + + wave B + + + two cycles of each; only the timing of each harmonic differs + diff --git a/content/quizzes/how-music-works/semitones-log-spacing.svg b/content/quizzes/how-music-works/semitones-log-spacing.svg new file mode 100644 index 0000000..15aa0a4 --- /dev/null +++ b/content/quizzes/how-music-works/semitones-log-spacing.svg @@ -0,0 +1,84 @@ + + + + + the 13 equal-tempered notes from A3 to A4, on a plain hertz scale + + + A + + A♯ + + B + + C + + C♯ + + D + + D♯ + + E + + F + + F♯ + + G + + G♯ + + A + + 13.1 Hz + + 24.7 Hz + 220 Hz + 440 Hz + every step multiplies by the same 1.0595, so the gaps in hertz keep growing + diff --git a/content/quizzes/how-music-works/spiral-of-fifths.svg b/content/quizzes/how-music-works/spiral-of-fifths.svg new file mode 100644 index 0000000..d8ace35 --- /dev/null +++ b/content/quizzes/how-music-works/spiral-of-fifths.svg @@ -0,0 +1,92 @@ + + + + + twelve pure 3:2 fifths from C, placed around the octave circle + + + + + + + + + + + + + + + C · 0 + + 1 · G + + 2 · D + + 3 · A + + 4 · E + + 5 · B + + 6 · F♯ + + 7 · C♯ + + 8 · G♯ + + 9 · D♯ + + 10 · A♯ + + 11 · E♯ + + 12 · B♯ + + ? + angle = pitch within the octave · distance from center = fifths stacked + diff --git a/content/quizzes/how-music-works/string-mode-shapes.svg b/content/quizzes/how-music-works/string-mode-shapes.svg new file mode 100644 index 0000000..afda93f --- /dev/null +++ b/content/quizzes/how-music-works/string-mode-shapes.svg @@ -0,0 +1,74 @@ + + + + + shapes a string fixed at both ends can vibrate in, all at once + + + + + shape 1 + 100 Hz + + + + + shape 2 + ? Hz + + + + + shape 3 + ? Hz + + + + + shape 4 + ? Hz + diff --git a/content/quizzes/how-music-works/two-spectra-x-y.svg b/content/quizzes/how-music-works/two-spectra-x-y.svg new file mode 100644 index 0000000..c1bf013 --- /dev/null +++ b/content/quizzes/how-music-works/two-spectra-x-y.svg @@ -0,0 +1,96 @@ + + + + + two instruments playing the same low note (147 Hz) + instrument X + + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 + + 10 + harmonic number + instrument Y + + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 + + 10 + harmonic number + diff --git a/content/quizzes/names-that-break-software.json b/content/quizzes/names-that-break-software.json new file mode 100644 index 0000000..bdda54f --- /dev/null +++ b/content/quizzes/names-that-break-software.json @@ -0,0 +1,258 @@ +{ + "schemaVersion": 1, + "id": "names-that-break-software", + "title": "Names that break software: NULL, True and little Bobby Tables", + "description": "A man picks the licence plate NULL and ends up with $12,049 in strangers' parking fines. A woman named Null can't book a flight online. An actor named True gets locked out of iCloud for six months. A whole English town can't sign up for AOL. Each Question starts with a real person or place, asks what the software got wrong, and ends with the fix.", + "language": "en", + "tags": ["sql", "security", "i18n", "debugging"], + "author": "Quizbun", + "questions": [ + { + "id": "null-plate-what-happened", + "title": "In late 2016, a security researcher registered this California vanity plate as a programmer's joke. What did it bring him?", + "description": "Joseph Tartaro, known as Droogie, chose `NULL` because in many programming languages it means \"no value\".", + "type": "single-choice", + "images": [ + { + "src": "null-licence-plate.svg", + "alt": "A plain, generic licence plate with the word NULL in large capitals and the small label vanity plate underneath.", + "caption": "Generic illustration, not California's plate design. Image: Quizbun.", + "width": 560, + "height": 300 + }, + { + "src": "null-plate-data-path.svg", + "alt": "Two rows of three boxes. Top row, labelled bug, blank plate saved as the text NULL: a parking ticket for a Honda with the plate left blank becomes a citation record with plate equal to the string NULL, and the owner lookup finds NULL equals NULL, a match, so the fine goes to the owner of the NULL plate. Bottom row, labelled fix, blank plate saved as a real NULL: the same ticket becomes a record with plate equal to NULL, no value at all, and the lookup NULL equals the string NULL gives unknown, so there is no match and the ticket is sent for review.", + "caption": "Simplified reconstruction of the reported mechanism. Diagram: Quizbun.", + "placement": "explanation", + "width": 720, + "height": 460 + } + ], + "options": [ + { "text": "Parking tickets written without a plate number were matched to his car, so strangers' fines were mailed to him", "isCorrect": true }, + { "text": "Plate-reading cameras skipped his car, so he never got a ticket again", "isCorrect": false }, + { "text": "The DMV system crashed each time an officer searched for his plate", "isCorrect": false }, + { "text": "Nothing: the DMV rejected the plate as a reserved word before it was issued", "isCorrect": false } + ], + "explanation": "Hundreds of tickets for Hondas, Toyotas and Mercedes arrived from all over California. He drives an Infiniti, and he says some of the tickets dated from 2014, before he even had the plate.\n\nThe tickets came through the Citation Processing Center, a private company that handles tickets for many California cities. When an officer left the plate field empty, the record reportedly stored `NULL` as text. To a database, the four letters N-U-L-L are an ordinary string, and they match a real plate that reads `NULL`. So every ticket without a plate matched his car.\n\nCameras play no part here. The officer left the plate field empty, and the system stored that empty field the wrong way.", + "references": "[WIRED: How a 'NULL' License Plate Landed One Hacker in Ticket Hell (2019)](https://www.wired.com/story/null-license-plate-landed-one-hacker-ticket-hell/), [Gizmodo: Security researcher's 'NULL' vanity plates cause glitch](https://gizmodo.com/security-researcher-s-null-vanity-plates-cause-glitch-1837206261)" + }, + { + "id": "null-plate-fine-total", + "title": "At the peak, how much in other people's fines was charged to the NULL plate, in US dollars?", + "description": "Type a number. Anything within about $1,000 counts.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [12049], + "tolerance": 1049 + }, + "explanation": "WIRED reported that at one point he faced **$12,049** in fines that weren't his. Most tickets were $37 to $80 each, for parking and stopping violations, so that's well over a hundred tickets.\n\nThe DMV and the processing company cancelled most of them, and by his DEF CON talk in August 2019 the total was down to $6,262. New tickets kept coming, though. Cancelling fines cleans up the data but leaves the bug in place, so every new ticket without a plate still matched his car.", + "references": "[WIRED: How a 'NULL' License Plate Landed One Hacker in Ticket Hell (2019)](https://www.wired.com/story/null-license-plate-landed-one-hacker-ticket-hell/)" + }, + { + "id": "null-plate-trigger", + "title": "For more than a year, the plate caused no trouble. What set off the flood of tickets?", + "type": "single-choice", + "options": [ + { "text": "He paid a genuine $35 ticket in early 2018, which linked the plate NULL to his name and address", "isCorrect": true }, + { "text": "He renewed his registration online in 2017, and the DMV website rejected his plate", "isCorrect": false }, + { "text": "He gave a talk about the plate at DEF CON, and someone reported him", "isCorrect": false }, + { "text": "A new plate-reading camera network came online in Los Angeles", "isCorrect": false } + ], + "explanation": "In early 2018 he got a real ticket, $35 for a missing registration sticker, and paid it. According to WIRED, the payment seems to have linked `NULL` to his name and address in the processing center's database. From then on, every ticket stored with a `NULL` plate had somewhere to go.\n\nThe 2017 renewal was a real early warning. The DMV website said his plate and VIN were invalid, and he renewed with a reference number instead. That bug lived in a website form, though, and didn't touch the ticket system. The DEF CON talk came in 2019, long after the tickets started.\n\nBugs like this are common. Bad data can sit unnoticed for a long time until another record gives it something to join with.", + "references": "[WIRED: How a 'NULL' License Plate Landed One Hacker in Ticket Hell (2019)](https://www.wired.com/story/null-license-plate-landed-one-hacker-ticket-hell/)" + }, + { + "id": "null-plate-fix", + "title": "Which changes to the ticket system would stop tickets with no plate from reaching the NULL driver?", + "type": "multiple-choice", + "options": [ + { "text": "Store a missing plate as SQL `NULL`, not as the text `'NULL'`", "isCorrect": true }, + { "text": "Refuse to look up an owner when the plate is missing, and send the ticket for manual review", "isCorrect": true }, + { "text": "Replace the text `'NULL'` with a different placeholder, such as `'NONE'`", "isCorrect": false }, + { "text": "Compare plates case-sensitively, so `'NULL'` no longer matches `'null'`", "isCorrect": false } + ], + "explanation": "SQL `NULL` means \"no value\", and it never equals anything, not even another `NULL`. When `plate` is a real `NULL`, the comparison `plate = 'NULL'` returns *unknown*, and a `WHERE` clause keeps only rows where the condition is true. So a real `NULL` can't match anybody.\n\n```sql\n-- The bug: a missing plate stored as a four-letter string\nSELECT owner FROM plates WHERE plate = 'NULL'; -- finds the NULL driver\n\n-- The fix: store no value, then check for it explicitly\nSELECT * FROM citations WHERE plate IS NULL; -- the tickets that need review\n```\n\nThe second fix is a safety net. If the plate is empty, the code skips the owner lookup and a person reviews the ticket.\n\nA new placeholder just moves the problem. Plates such as `NONE`, `NOPLATE` or `MISSING` are just as easy to register, and whoever gets one inherits the bug. Case-sensitive matching doesn't help either, because the plate and the placeholder are both uppercase `NULL`.\n\nThe SQL query detective Quiz covers how `NULL` behaves in comparisons, joins and aggregates.", + "references": "[Wikipedia: Null (SQL)](https://en.wikipedia.org/wiki/Null_(SQL)), [WIRED: How a 'NULL' License Plate Landed One Hacker in Ticket Hell (2019)](https://www.wired.com/story/null-license-plate-landed-one-hacker-ticket-hell/)" + }, + { + "id": "jennifer-null-flights", + "title": "Jennifer Null from Virginia tries to book a flight online. What happens on most airline websites?", + "type": "single-choice", + "options": [ + { "text": "The site says she left the surname field blank and asks her to try again", "isCorrect": true }, + { "text": "The booking goes through, but her ticket prints with no name on it", "isCorrect": false }, + { "text": "The site flags her as a possible security threat and blocks the booking", "isCorrect": false }, + { "text": "The site charges her card and then loses the booking", "isCorrect": false } + ], + "explanation": "She told the BBC in 2016 that most sites say she left the surname field empty. She has to phone the airline instead, and she's sometimes been told her story can't be true.\n\nThe likely cause is a check that treats the text `\"Null\"` as \"no value\". Some code turns a missing value into the string `\"null\"` when it serializes data, so a later check that sees `\"null\"` can't tell an empty field from her real surname.\n\nIt's the NULL plate bug in reverse. The ticket system turned missing data into a name, and airline sites turn her name into missing data.", + "references": "[BBC Future: Hello, I'm Mr Null. My name makes me invisible to computers (2016)](https://www.bbc.com/future/article/20160325-the-names-that-break-computer-systems)" + }, + { + "id": "rachel-true-lockout", + "title": "In March 2021, actor and author Rachel True said she'd been locked out of her iCloud account. How long had it been going on?", + "type": "single-choice", + "options": [ + { "text": "About six months", "isCorrect": true }, + { "text": "About two days", "isCorrect": false }, + { "text": "About two weeks", "isCorrect": false }, + { "text": "About three years", "isCorrect": false } + ], + "explanation": "Rachel True, known for the 1996 film *The Craft*, said she hadn't been able to use iCloud for about **six months**. She'd spent more than six hours on the phone with support over several days, and Apple kept charging her for storage she couldn't reach.\n\nAfter she posted the error message she kept seeing, the story went viral and a member of the iCloud team offered to help. The next Question looks at that message.", + "references": "[The Register: Name True, iCloud access false (2021)](https://www.theregister.com/2021/03/08/name_true_apple_icloud_access/), [9to5Mac: iCloud coding error locks out Rachel True over a boolean flag (2021)](https://9to5mac.com/2021/03/07/icloud-user-locked-out-for-six-months-over-coding-bug-related-to-her-last-name/)" + }, + { + "id": "rachel-true-cause", + "title": "Rachel True's error message read: `Type error: cannot set value 'true' to property 'lastName'`. What does it suggest went wrong?", + "type": "single-choice", + "images": [ + { + "src": "true-string-to-boolean.svg", + "alt": "A three-step flow. A form field labelled last name contains True. Read without quotes, it becomes lastName colon true in lowercase, a boolean. A schema check that expects lastName to be a string then raises a type error. Below, two boxes compare the quoted string True, her surname as text, with the bare boolean true, a yes or no value that is not a name.", + "caption": "Illustrative. Apple has not published the cause. Diagram: Quizbun.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "options": [ + { "text": "Somewhere, her surname was read as the boolean `true`, and the `lastName` field only accepts strings", "isCorrect": true }, + { "text": "Her surname was on a blocklist of reserved words, and the account was frozen", "isCorrect": false }, + { "text": "Her surname was too short to meet a minimum length rule", "isCorrect": false }, + { "text": "Her surname matched a SQL keyword and was treated as an injection attempt", "isCorrect": false } + ], + "explanation": "The message names the field, `lastName`, and the value, a lowercase `true`, which is a boolean. Somewhere between her keyboard and Apple's servers, a parser or converter saw the four letters without quotes and turned them into a yes/no value. The next step expected a string and rejected the boolean.\n\nThe fix is to keep text as text the whole way through. Send names as quoted JSON strings, declare the field as a string in the schema, and never let a generic converter guess a type from how a value looks.\n\nApple didn't publish a post-mortem, so this explanation comes from the error message alone. The other Options don't fit that message. A blocklist, a length rule or an injection filter would reject the name itself. None of them would complain that a `true` value can't go into a text field.", + "references": "[The Register: Name True, iCloud access false (2021)](https://www.theregister.com/2021/03/08/name_true_apple_icloud_access/), [Engadget: iCloud allegedly locked out a user over her last name (2021)](https://www.engadget.com/icloud-true-last-name-lock-out-192224309.html)" + }, + { + "id": "yaml-norway-problem", + "title": "A YAML config file lists country codes. A YAML 1.1 parser such as PyYAML loads the list below. What does Norway's `NO` become?", + "description": "```yaml\ncountries:\n - GB\n - FR\n - NO\n```\n\nType the value as the parser would print it.", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": ["false", "False", "boolean false", "the boolean false", "bool false"] + }, + "explanation": "YAML 1.1 reads a bare `NO` as the boolean **false**. Its type repository lists `y`, `Y`, `yes`, `Yes`, `YES`, `on`, `On`, `ON` and the `true` spellings as true, and `n`, `N`, `no`, `No`, `NO`, `off`, `Off`, `OFF` and the `false` spellings as false. So the list loads as `['GB', 'FR', False]`, and Norway disappears. Developers call this the Norway problem.\n\nYAML 1.2 (2009) narrowed its core schema to `true` and `false`, but many popular parsers, including PyYAML, still follow 1.1.\n\nThe fix is the same as for Rachel True: mark it as text. Write `\"NO\"` in quotes, or use a parser mode or schema that loads strings as strings.", + "references": "[YAML 1.1: Boolean Language-Independent Type](https://yaml.org/type/bool.html), [YAML 1.2.2 specification](https://yaml.org/spec/1.2.2/)" + }, + { + "id": "scunthorpe-aol-filter", + "title": "In 1996, people in Scunthorpe, an English town in North Lincolnshire, couldn't sign up for AOL. Why?", + "type": "single-choice", + "images": [ + { + "src": "substring-filter-town-name.svg", + "alt": "Two panels using the harmless example word hell and the village name Hellifield. The substring check, name to lower case includes hell, highlights the first four letters of Hellifield and blocks it. The whole-word check, a regular expression with word boundaries around hell, allows Hellifield because hell is only part of a longer word.", + "caption": "The same mechanism shown with a milder word. Diagram: Quizbun.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "options": [ + { "text": "AOL's profanity filter found a rude word hidden inside the town's name", "isCorrect": true }, + { "text": "AOL didn't yet offer service to that part of England", "isCorrect": false }, + { "text": "The town's name was longer than AOL's address field allowed", "isCorrect": false }, + { "text": "The town's dial-up exchange wasn't compatible with AOL's modems", "isCorrect": false } + ], + "explanation": "AOL's filter looked for banned words *anywhere* in the text, and one hides in letters two to five of \"Scunthorpe\". A substring check doesn't know where words start or end, so it blocked the whole town.\n\nThe diagram uses a milder word. Hellifield, a village in North Yorkshire, contains `hell`, so a substring filter for `hell` blocks it too.\n\nThis case gave the bug its name. When a filter blocks innocent text because of a hidden substring, that's the **Scunthorpe problem**. Penistone, Clitheroe and London's Horniman Museum have all run into it too.", + "references": "[Wikipedia: Scunthorpe problem](https://en.wikipedia.org/wiki/Scunthorpe_problem)" + }, + { + "id": "scunthorpe-renamed", + "title": "AOL's first response didn't fix the filter. What did it tell Scunthorpe residents to do?", + "type": "single-choice", + "options": [ + { "text": "Enter the town as \"Sconthorpe\"", "isCorrect": true }, + { "text": "Enter a nearby town's name instead", "isCorrect": false }, + { "text": "Write to AOL for a manual exemption", "isCorrect": false }, + { "text": "Leave the town field blank", "isCorrect": false } + ], + "explanation": "Resident Doug Blackie found that AOL would register him only if he typed the town as **Sconthorpe**. Changing one letter hid the banned substring. The *Scunthorpe Evening Telegraph* covered it on 9 April 1996, and RISKS Digest summed it up: as far as AOL was concerned, the town would \"henceforth be known as\" Sconthorpe.\n\nThis workaround changes the user's data to suit the filter, so the next town or surname with a hidden match hits the same bug. AOL did the same to Craig Cockburn, whose surname contains a banned word. At first it told him to replace the first \"o\" with a zero.", + "references": "[RISKS Digest 18.07: AOL and Scunthorpe (1996)](https://catless.ncl.ac.uk/Risks/18.07.html), [Wikipedia: Scunthorpe problem](https://en.wikipedia.org/wiki/Scunthorpe_problem)" + }, + { + "id": "word-boundary-limits", + "title": "A developer switches the profanity filter from substring matching to whole-word matching, like `\\bword\\b`. Which problems remain?", + "type": "multiple-choice", + "options": [ + { "text": "A person whose surname is itself a word on the list is still blocked", "isCorrect": true }, + { "text": "Anyone who adds spaces, dots or digits inside a banned word gets past the filter", "isCorrect": true }, + { "text": "Languages written without spaces between words, such as Chinese, Japanese and Thai, have no word boundaries for `\\b` to find", "isCorrect": true }, + { "text": "Scunthorpe is still blocked", "isCorrect": false } + ], + "explanation": "Whole-word matching fixes Scunthorpe itself. In a regular expression, `\\b` matches only at the edge of a word, so a banned word buried inside \"Scunthorpe\" no longer matches.\n\nThree problems remain:\n\n- **Names that are on the list.** Some surnames are also English words that a filter might ban. Whole-word matching blocks them just as before.\n- **Evasion.** Put spaces, dots or digits between the letters of a rude word and it isn't a whole word anymore, so it gets through.\n- **Scripts without spaces.** In JavaScript, `\\b` treats only ASCII letters, digits and `_` as word characters, and Chinese, Japanese and Thai don't put spaces between words. The filter either matches inside other words again or matches nothing at all.\n\nA filter can't tell what a name means. For names and places, it's safer to skip automatic blocking and send doubtful cases to a person.", + "references": "[Wikipedia: Scunthorpe problem](https://en.wikipedia.org/wiki/Scunthorpe_problem), [MDN: Word boundary assertion](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Word_boundary_assertion)" + }, + { + "id": "bobby-tables-fix", + "title": "In xkcd's \"Exploits of a Mom\", a school loses its whole student table because of one pupil's name. Which changes would stop this from happening again?", + "description": "A mother named her son `Robert'); DROP TABLE Students;--`, or little Bobby Tables for short. The school's code builds its query like this:\n\n```js\ndb.query(\"INSERT INTO Students (name) VALUES ('\" + name + \"');\");\n```", + "type": "multiple-choice", + "options": [ + { "text": "Use a prepared statement and pass the name as a bound parameter", "isCorrect": true }, + { "text": "Use the ORM's normal insert method, which binds values as parameters", "isCorrect": true }, + { "text": "Remove apostrophes from every name before building the query", "isCorrect": false }, + { "text": "Reject any name that contains the word `DROP`", "isCorrect": false } + ], + "explanation": "Pasted into the query, the name closes the string with `')`, ends the statement with `;`, adds its own `DROP TABLE Students;` and comments out the rest with `--`. The database can't tell the school's SQL from the pupil's name, because they arrive as one piece of text.\n\nA bound parameter sends the SQL and the value separately:\n\n```js\ndb.query(\"INSERT INTO Students (name) VALUES (?)\", [name]);\n```\n\nThe database parses the query first, then treats `name` as data, whatever characters it contains. An ORM's normal methods do the same thing under the hood. OWASP lists parameterized queries as the first defence against SQL injection.\n\nStripping apostrophes breaks real names such as O'Brien. It also misses injections that need no quote at all, such as a query built as `\"... WHERE id = \" + id`. Banning `DROP` is a word filter with the Scunthorpe problem both ways. It blocks harmless text that contains \"drop\", and an attacker can still delete or read data with other statements.", + "references": "[xkcd 327: Exploits of a Mom](https://xkcd.com/327/), [OWASP: SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html)" + }, + { + "id": "falsehoods-count", + "title": "In June 2010, Patrick McKenzie published \"Falsehoods Programmers Believe About Names\". How many falsehoods are on the list?", + "type": "single-choice", + "options": [ + { "text": "40", "isCorrect": true }, + { "text": "10", "isCorrect": false }, + { "text": "25", "isCorrect": false }, + { "text": "101", "isCorrect": false } + ], + "explanation": "The list has **40** numbered items. It opens with \"People have exactly one canonical full name\" and ends with \"People have names.\" The items get more exasperated as it goes, and several come in escalating runs, such as a name being assigned at birth, then within a year, then within five years.\n\nEach item is an assumption that real software makes, and each one is wrong for someone. The post started a whole genre, and there are now \"Falsehoods programmers believe\" lists for time, addresses, phone numbers and email.", + "references": "[Patrick McKenzie: Falsehoods Programmers Believe About Names (2010)](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/)" + }, + { + "id": "falsehoods-inspiration", + "title": "McKenzie's post opens by pointing to another programmer's complaint from the same day. What happened to John Graham-Cumming?", + "type": "single-choice", + "options": [ + { "text": "A website told him his last name contained invalid characters, because of the hyphen", "isCorrect": true }, + { "text": "A bank merged his account with another customer who had the same name", "isCorrect": false }, + { "text": "An airline printed his name in all capitals, and security rejected his boarding pass", "isCorrect": false }, + { "text": "A form cut his surname off after the first ten letters", "isCorrect": false } + ], + "explanation": "On 17 June 2010, Graham-Cumming blogged about a website that answered \"Graham-Cumming\" with an error saying his last name contained invalid characters. He pointed out that the site really meant it wouldn't accept the hyphen, and that calling part of someone's name \"invalid\" is rude.\n\nThe bug usually comes from a validation pattern like `^[A-Za-z]+$`. It looks tidy, but it rejects hyphens, apostrophes, spaces and every letter outside basic Latin. The W3C advises name fields to accept every character people use and to validate as little as possible.", + "references": "[John Graham-Cumming: Your last name contains invalid characters (2010)](https://blog.jgc.org/2010/06/your-last-name-contains-invalid.html), [W3C: Personal names around the world](https://www.w3.org/International/questions/qa-personal-names)" + }, + { + "id": "hawaii-name-too-long", + "title": "Janice \"Lokelani\" Keihanaikukauakahihuliheekahaunaele's surname is 36 characters long. In 2013, Hawaii's ID system allowed 35. What did her driver's licence show?", + "type": "multiple-choice", + "options": [ + { "text": "Her surname with the final letter cut off", "isCorrect": true }, + { "text": "No first name at all", "isCorrect": true }, + { "text": "Her surname abbreviated to its first ten letters", "isCorrect": false }, + { "text": "Her maiden name in place of her married name", "isCorrect": false } + ], + "explanation": "Her surname has 35 letters plus an ʻokina, the Hawaiian glottal-stop mark, for 36 characters in total. Hawaii County's system cut it to fit the 35-character limit, and her licence and state ID left out her first name entirely. After a police officer gave her a hard time over the licence at a traffic stop, she wrote to Honolulu station KHON, and the story spread in September 2013.\n\nThe state then updated its system to allow 40 characters for first and last names, and at the end of December 2013 she got a licence with her full name.\n\nA fixed-width name field is a guess about the longest name anyone will have. When the guess is wrong, silently cutting the name off is the worst outcome, because the card then shows a name that belongs to nobody and doesn't match her other records.", + "references": "[CBS News: Hawaii woman's last name so long it won't fit on her license (2013)](https://www.cbsnews.com/news/hawaii-womans-last-name-so-long-it-wont-fit-on-her-license/), [Fox News: Hawaii woman with long last name forces state to alter ID policy (2013)](https://www.foxnews.com/us/hawaii-woman-with-long-last-name-forces-state-to-alter-id-policy)" + }, + { + "id": "boss-which-are-falsehoods", + "title": "Final boss: which of these assumptions about names are false?", + "description": "Every person in this Quiz ran into at least one of them.", + "type": "multiple-choice", + "options": [ + { "text": "People's names do not change", "isCorrect": true }, + { "text": "People's names fit within a certain defined amount of space", "isCorrect": true }, + { "text": "People's names are written in ASCII", "isCorrect": true }, + { "text": "People have last names, family names, or anything else shared with their relatives", "isCorrect": true } + ], + "explanation": "All four are false. Each one paraphrases an item on McKenzie's list, and each has a real counterexample:\n\n- **Names change.** Jennifer Null took her surname when she married, and the new name broke airline websites.\n- **Names don't fit a fixed width.** Hawaii's 35-character field cut off the end of a real surname.\n- **Names aren't ASCII.** Written Hawaiian uses the ʻokina and the kahakō. Most of the world's names use characters outside ASCII, and a pattern like `^[A-Za-z]+$` rejects them.\n- **Not everyone has a family name.** Many Indonesians go by a single name, and Icelandic surnames are patronymics that differ between parent and child.\n\nThe fix for all of them is the same. Store a name as a Unicode string in one generous field, keep it exactly as the person typed it, and never treat it as a boolean, a missing value, a rude word or code.", + "references": "[Patrick McKenzie: Falsehoods Programmers Believe About Names (2010)](https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/), [W3C: Personal names around the world](https://www.w3.org/International/questions/qa-personal-names)" + } + ] +} diff --git a/content/quizzes/names-that-break-software/null-licence-plate.svg b/content/quizzes/names-that-break-software/null-licence-plate.svg new file mode 100644 index 0000000..c3693b6 --- /dev/null +++ b/content/quizzes/names-that-break-software/null-licence-plate.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + NULL + VANITY PLATE + diff --git a/content/quizzes/names-that-break-software/null-plate-data-path.svg b/content/quizzes/names-that-break-software/null-plate-data-path.svg new file mode 100644 index 0000000..d0fc5aa --- /dev/null +++ b/content/quizzes/names-that-break-software/null-plate-data-path.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + Where a ticket with no plate ends up + + + + + Bug: blank plate saved as the text "NULL" + + + Parking ticket + Car: Honda + Plate: (left blank) + + + + + Citation record + plate = 'NULL' + a four-letter string + + + + + Owner lookup + 'NULL' = 'NULL' + Match: fine mailed out + + to the owner of the NULL plate + + + + + + Fix: blank plate saved as a real NULL + + + Parking ticket + Car: Honda + Plate: (left blank) + + + + + Citation record + plate = NULL + no value at all + + + + + Owner lookup + NULL = 'NULL' → unknown + No match: sent for review + + Simplified. The processing center's real database schema isn't public. + + diff --git a/content/quizzes/names-that-break-software/substring-filter-town-name.svg b/content/quizzes/names-that-break-software/substring-filter-town-name.svg new file mode 100644 index 0000000..f28ccda --- /dev/null +++ b/content/quizzes/names-that-break-software/substring-filter-town-name.svg @@ -0,0 +1,74 @@ + + + + + + Substring check vs whole-word check + + + Banned word: hell. Name typed in: Hellifield, a real village in North Yorkshire. + + + + + + Substring check + name.toLowerCase().includes("hell") + + + Hellifield + Blocked + + + + Whole-word check + /\bhell\b/i.test(name) + + Hellifield + "hell" is only part of a word + Allowed + + Scunthorpe failed the same check in 1996, with a ruder word inside it. + + diff --git a/content/quizzes/names-that-break-software/true-string-to-boolean.svg b/content/quizzes/names-that-break-software/true-string-to-boolean.svg new file mode 100644 index 0000000..4f5f605 --- /dev/null +++ b/content/quizzes/names-that-break-software/true-string-to-boolean.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + How a surname can turn into a boolean + + + + + + Last name + + True + what she typed + + + + + + Read without quotes + lastName: true + so it becomes a boolean + + + + + + Schema check + lastName must be + a string + Type error + + + + + Same four letters, two different values + + + "True" + string: her surname, as text + + + true + boolean: yes or no, not a name + + Illustrative. Apple hasn't published the cause, so this follows the error message. + + diff --git a/content/quizzes/programmer-habits-under-the-microscope.json b/content/quizzes/programmer-habits-under-the-microscope.json new file mode 100644 index 0000000..c26c0f8 --- /dev/null +++ b/content/quizzes/programmer-habits-under-the-microscope.json @@ -0,0 +1,497 @@ +{ + "schemaVersion": 1, + "id": "programmer-habits-under-the-microscope", + "title": "Programmer habits under the microscope: Fridays, night owls, swearing and music", + "description": "Don't deploy on a Friday. Real programmers work at night. Swearing in comments means messy code. Music ruins concentration. Researchers have tested all four with commit histories, bug trackers and field experiments. Each Question takes one belief, looks at the study behind it, and asks what the study found and how far you can trust it. For developers, team leads, and anyone who has quoted a study in a code review.", + "language": "en", + "tags": [ + "work", + "psychology", + "statistics", + "critical-thinking" + ], + "author": "Quizbun", + "questions": [ + { + "id": "dont-program-on-fridays", + "title": "A 2005 paper was titled \"Don't Program on Fridays!\" What did it actually find?", + "type": "single-choice", + "options": [ + { + "text": "In Eclipse's history, changes that later needed a fix were most common on Fridays", + "isCorrect": true + }, + { + "text": "Friday deployments caused more production outages at large companies", + "isCorrect": false + }, + { + "text": "Programmers made more typing errors on Fridays, according to keystroke logs", + "isCorrect": false + }, + { + "text": "Nothing: the title was a joke, and the data showed no weekday pattern", + "isCorrect": false + } + ], + "explanation": "Jacek Śliwerski, Thomas Zimmermann and Andreas Zeller mined the version history of **Eclipse**, the open source IDE. For each weekday they measured the share of changes that later turned out to be *fix-inducing*: a later bug fix had to touch the same lines. On Fridays that share was **12.2%**, the highest of the week. On Tuesdays it was **10.4%**, and the weekly average was 11.4%.\n\nLook at how small the gap is before you ban Friday commits. Thursday came in at 12.1%, a tenth of a point behind Friday. The paper is two pages long and calls itself \"a first investigation\" of one project. It studied commits, not deployments, and it never mentions outages.\n\nThe title did the rest, because \"Don't program on Fridays\" is easier to repeat than \"fix-inducing changes were slightly more frequent on Fridays in Eclipse\".", + "references": "[Śliwerski, Zimmermann, Zeller: Don't Program on Fridays! How to Locate Fix-Inducing Changes (2005, PDF)](https://thomas-zimmermann.com/publications/files/sliwerski-wsr-2005.pdf), [ACM: When do changes induce fixes? (MSR 2005)](https://doi.org/10.1145/1083142.1083147)", + "images": [ + { + "src": "eclipse-fix-inducing-by-weekday.svg", + "alt": "Bar chart of the share of Eclipse changes that later needed a fix, by weekday, on an axis from 0 to 14 percent: Monday 11.3, Tuesday 10.4, Wednesday 11.1, Thursday 12.1, Friday 12.2 (highlighted), Saturday 11.7, Sunday 11.6. The bars look almost equal in height.", + "caption": "Numbers from Table 1 of Śliwerski, Zimmermann and Zeller (2005). Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 424 + } + ] + }, + { + "id": "how-to-find-fix-inducing-changes", + "title": "How did the researchers decide which earlier change caused a later fix?", + "type": "single-choice", + "options": [ + { + "text": "They traced the lines each bug fix changed back to the change that last touched them, then dropped changes made after the bug was reported", + "isCorrect": true + }, + { + "text": "They ran the test suite against every old revision and flagged the first revision where a test failed", + "isCorrect": false + }, + { + "text": "They surveyed Eclipse developers and asked which of their own changes had caused bugs", + "isCorrect": false + }, + { + "text": "They flagged any change whose commit message contained the word \"bug\"", + "isCorrect": false + } + ], + "explanation": "The method has two steps, and both are automatic.\n\n1. **Find the fixes.** A commit counts as a fix when its message mentions a bug number from the bug database (Bugzilla, in Eclipse's case) or keywords such as \"fixed\".\n2. **Trace back.** For every line the fix changed, ask the version control system who last touched that line and in which revision. In CVS that was `cvs annotate`. Today you would run `git blame`. Those earlier revisions are the candidates.\n\nThe bug database then removes false suspects. A change made *after* the bug was reported can't have caused it, so it drops out.\n\nThe method became known as **SZZ**, after the authors' initials, and it's still the standard way to label bug-introducing commits in research. It has one built-in weakness. The last change to a line isn't always the one that caused the bug. The authors say so, and call these changes *fix-inducing*, which is more modest than *bug-introducing*.\n\nRunning old revisions against tests is what `git bisect` does. That works for one bug you can reproduce, but not for thousands of historical bugs without tests.", + "references": "[Śliwerski, Zimmermann, Zeller: Don't Program on Fridays! How to Locate Fix-Inducing Changes (2005, PDF)](https://thomas-zimmermann.com/publications/files/sliwerski-wsr-2005.pdf), [ACM: When do changes induce fixes? (MSR 2005)](https://doi.org/10.1145/1083142.1083147)" + }, + { + "id": "read-the-eclipse-table", + "title": "Which statements does the Eclipse table support?", + "description": "The paper's Table 1, in percent. `P(fix)` is the share of changes that were fixes. `P(bug)` is the share that were fix-inducing. `P(bug | fix)` is the share of *fixes* that later needed another fix, and `P(bug | ¬fix)` is the same for all other changes.\n\n| | Mon | Tue | Wed | Thu | Fri | Sat | Sun | avg |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n| P(fix) | 18.4 | 20.9 | 20.0 | 22.3 | 24.0 | 14.7 | 16.9 | 20.8 |\n| P(bug) | 11.3 | 10.4 | 11.1 | 12.1 | 12.2 | 11.7 | 11.6 | 11.4 |\n| P(bug \\| fix) | 25.1 | 22.9 | 23.3 | 23.5 | 23.2 | 30.3 | 26.4 | 23.7 |\n| P(bug \\| ¬fix) | 8.2 | 7.1 | 8.1 | 8.8 | 8.7 | 8.4 | 8.6 | 8.1 |", + "type": "multiple-choice", + "options": [ + { + "text": "Fixes were the riskiest kind of change: about 24% of them needed another fix, against about 8% of other changes", + "isCorrect": true + }, + { + "text": "Friday's rate of fix-inducing changes was less than 2 percentage points above Tuesday's", + "isCorrect": true + }, + { + "text": "Fixes made on Saturday were the most likely to need another fix", + "isCorrect": true + }, + { + "text": "A change made on Friday was more than twice as likely to need a fix as one made on Tuesday", + "isCorrect": false + }, + { + "text": "Weekend changes were the safest of the week", + "isCorrect": false + } + ], + "explanation": "The most striking row isn't about Fridays. A fix went on to need another fix **23.7%** of the time, and an ordinary change only **8.1%** of the time, so a fix was almost three times riskier. The authors point this out themselves. Fixes are often rushed and touch code that has already proved fragile.\n\nFriday versus Tuesday is 12.2% against 10.4%, a gap of 1.8 percentage points. That's about 17% more in relative terms, nowhere near double. Headlines tend to quote the relative figure because it sounds bigger.\n\nSaturday fixes had the highest follow-up rate, 30.3%, but they were also the rarest. Only 14.7% of Saturday changes were fixes, so that percentage rests on fewer changes. And weekends weren't the safest: Saturday (11.7%) and Sunday (11.6%) sat above Monday, Tuesday and Wednesday.\n\nNow compare the first two rows. Friday had the **most fixes** (24.0%) *and* the most fix-inducing changes. If fixes are three times riskier, part of the \"Friday effect\" may really be a \"fix effect\". The next Question returns to this.", + "references": "[Śliwerski, Zimmermann, Zeller: Don't Program on Fridays! How to Locate Fix-Inducing Changes (2005, PDF)](https://thomas-zimmermann.com/publications/files/sliwerski-wsr-2005.pdf)" + }, + { + "id": "does-friday-cause-bugs", + "title": "Does the Eclipse result show that deploying on a Friday causes bugs?", + "type": "single-choice", + "options": [ + { + "text": "No: it's one project's commit history, it shows a correlation, and it measured commits rather than deployments", + "isCorrect": true + }, + { + "text": "Yes: the pattern appeared across thousands of commits, so it can't be chance", + "isCorrect": false + }, + { + "text": "Yes: the authors controlled for developer experience and the size of each change", + "isCorrect": false + }, + { + "text": "No, because Eclipse developers didn't commit on Fridays", + "isCorrect": false + } + ], + "explanation": "The study is **observational**. Nobody assigned some changes to Friday and others to Tuesday. It counted what happened in one project, so anything else that differs between Fridays and other days could produce the pattern:\n\n- **What kind of work lands on Friday.** Eclipse had the most fixes on Fridays, and fixes were about three times riskier than other changes. Friday could look risky simply because it's fix day.\n- **Who commits on Friday**, and whether they're wrapping up a week's work in a hurry.\n- **One project, one era.** Eclipse's release rhythm in the early 2000s says little about your team's pipeline.\n\nA large number of commits makes chance less likely, but it doesn't fix confounding. Big data can measure a biased comparison very precisely. The authors didn't claim more than the data showed, either. They called it a first case study and listed day of week as one of many properties that *correlate* with fix-inducing changes.\n\nTo show that Fridays *cause* bugs, you'd need to compare like with like: the same developers, the same kind of change, across many projects, ideally with some changes randomly scheduled. Even then you'd have learned about committing, not deploying. Whether to deploy on Friday is mostly about who is around to respond if something breaks over the weekend.", + "references": "[Śliwerski, Zimmermann, Zeller: Don't Program on Fridays! How to Locate Fix-Inducing Changes (2005, PDF)](https://thomas-zimmermann.com/publications/files/sliwerski-wsr-2005.pdf)" + }, + { + "id": "tgif-commit-times-2015-2024", + "title": "A study published in 2025, \"TGIF\", tracked commit times in 4,549 GitHub repositories from 2015 to 2024. What did it find?", + "type": "multiple-choice", + "options": [ + { + "text": "The share of nighttime and weekend commits rose slightly over the decade", + "isCorrect": true + }, + { + "text": "Commit activity on the five workdays stayed stable, with no statistically significant difference between individual workdays", + "isCorrect": true + }, + { + "text": "Friday commits introduced more bugs than commits on any other workday", + "isCorrect": false + }, + { + "text": "After 2020, commits moved back toward a strict 9-to-5, Monday-to-Friday pattern", + "isCorrect": false + }, + { + "text": "Weekend commits dropped to almost zero", + "isCorrect": false + } + ], + "explanation": "Ioannis Karyotakis, Evangelos Talos and Diomidis Spinellis measured **when** commits happen, year by year. They found a subtle but consistent rise in nighttime and weekend commits, especially in the early morning hours, and read it as a gradual drift away from the 9-to-5, Monday-to-Friday week. Across the workdays themselves, nothing changed. No workday stood out from the others at a statistically significant level.\n\nThis doesn't update the 2005 Friday finding either way, because TGIF didn't measure bugs. It answers a different question: is Friday a special day for *how much* developers commit? In this sample, it wasn't.\n\nWhen a new paper seems to confirm or overturn an old one, first check whether the two measured the same thing. Here they didn't: one measured when fix-inducing changes happened, the other when any commit happened.", + "references": "[Empirical Software Engineering: TGIF: the evolution of developer commit times (Karyotakis, Talos, Spinellis, 2025)](https://doi.org/10.1007/s10664-025-10767-2), [Śliwerski, Zimmermann, Zeller: Don't Program on Fridays! How to Locate Fix-Inducing Changes (2005, PDF)](https://thomas-zimmermann.com/publications/files/sliwerski-wsr-2005.pdf)" + }, + { + "id": "night-owl-study-project-count", + "title": "How many large open source projects did Claes and colleagues study to find out when programmers work?", + "description": "The 2018 ICSE paper \"Do Programmers Work at Night or During the Weekend?\" mined commit timestamps from Mozilla and Apache projects, and compared them with one Finnish company. Type a whole number.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 86 + ] + }, + "explanation": "The answer is **86** open source projects: Mozilla products such as Firefox, Thunderbird and SeaMonkey, and 81 Apache projects. With the Finnish company's closed-source product, that makes 87 projects in total.\n\nThe Apache list started at 822 projects. The authors kept only those with at least 2,000 usable commits, because a small project doesn't have enough timestamps to show a weekly rhythm. They also dropped old Apache commits that had been imported from an earlier version control system without time zone information. A commit time without its time zone can't tell you whether someone worked at 03:00 or 12:00.\n\nThe Mozilla and Apache mix was deliberate. Mozilla pays many of its core Firefox developers. The Apache Software Foundation pays no developers, although some companies pay their own staff to work on Apache projects. That contrast comes back two Questions later.", + "references": "[arXiv: Do Programmers Work at Night or During the Weekend? (Claes, Mäntylä, Kuutila, Adams, ICSE 2018)](https://arxiv.org/abs/1802.05084)" + }, + { + "id": "do-programmers-work-at-night", + "title": "What did the study find about when most of the active developers commit?", + "type": "single-choice", + "options": [ + { + "text": "About two thirds kept to regular office hours, roughly 10:00 to 18:00, and rarely committed at night or on weekends", + "isCorrect": true + }, + { + "text": "Most of them committed mainly between 22:00 and 04:00", + "isCorrect": false + }, + { + "text": "Commits were spread evenly across all 24 hours, with no daily rhythm", + "isCorrect": false + }, + { + "text": "Most of them committed more on weekends, when meetings didn't interrupt them", + "isCorrect": false + } + ], + "explanation": "The authors took the top 10% of committers, **1,108** of 11,059 developers, each with at least 95 commits. For every developer they built a profile of 168 numbers, one for each hour of the week, and let a clustering algorithm (k-means) group similar profiles.\n\nTwo of the three clusters were office workers, together **66%** of the developers. Their commits rose through the morning, dipped at lunch around noon, and tailed off after 16:00. The two clusters differed mainly by about an hour, like early and late risers. The third cluster committed mostly from noon to midnight, and on weekends too.\n\nAcross projects, the median working day started at **10:03**, hardly a night-owl schedule. Weekends were quieter, and among weekdays Monday and Friday were the least active.\n\nThis kind of evidence is **descriptive**. It shows when commits happen. It says nothing about whether code written at night is better or worse.", + "references": "[arXiv: Do Programmers Work at Night or During the Weekend? (Claes, Mäntylä, Kuutila, Adams, ICSE 2018)](https://arxiv.org/abs/1802.05084)" + }, + { + "id": "share-of-commits-in-office-hours", + "title": "For each project, the researchers found its busiest 8-hour window on weekdays. On average, what share of a project's commits fell inside that window?", + "type": "single-choice", + "options": [ + { + "text": "About 60%, ranging from 31% to 91% across projects", + "isCorrect": true + }, + { + "text": "About 95%: almost every commit happened in office hours", + "isCorrect": false + }, + { + "text": "About 80% in every project, with little variation", + "isCorrect": false + }, + { + "text": "About a third, as if commits were spread evenly around the clock", + "isCorrect": false + } + ], + "explanation": "On average only about **60%** of a project's commits landed in its own 8-hour office window, with a standard deviation of 12 percentage points. Even with an hour added on each side, the average was 68%. So two thirds of *developers* keep office hours, yet about 40% of *commits* happen outside them. Both are true, because the night and weekend group, and the long tail of occasional contributors, still add plenty of commits.\n\nThe range matters more than the average. One project committed 91% of its work in office hours, and another only 31%. The projects with the most outside-hours activity were SeaMonkey, Thunderbird and Groovy, all run by their communities by then rather than by paid teams. Projects that worked a lot in the evenings also worked a lot on weekends: the correlation between the two was 0.77.\n\nThe authors expected projects to settle into office hours as they matured. They found no support for that. Over time, 27 projects moved toward office hours by 5 points or more, 18 moved away, and most barely changed.\n\nThat spread is why the authors conclude that stress detection from timestamps needs a baseline for each person. A commit at 23:00 is ordinary for one developer and a warning sign for another.", + "references": "[arXiv: Do Programmers Work at Night or During the Weekend? (Claes, Mäntylä, Kuutila, Adams, ICSE 2018)](https://arxiv.org/abs/1802.05084)" + }, + { + "id": "paid-versus-unpaid-firefox", + "title": "In Firefox, the researchers checked whether each top developer was paid by Mozilla or was a volunteer. What did they find?", + "type": "single-choice", + "options": [ + { + "text": "About half of the unpaid developers were in the evenings-and-weekends cluster, against about one in ten paid developers", + "isCorrect": true + }, + { + "text": "Paid and unpaid developers had practically the same working patterns", + "isCorrect": false + }, + { + "text": "Paid developers did most of the night work, because of release deadlines", + "isCorrect": false + }, + { + "text": "Unpaid developers committed almost only on weekends", + "isCorrect": false + } + ], + "explanation": "The team looked up each of Firefox's top developers on LinkedIn and personal websites to find out whether they were paid to work on Firefox. They then crossed that with the three work-pattern clusters. Of the 48 unpaid developers, **23 (47.9%)** landed in the evenings-and-weekends cluster. Of the 227 paid developers, also **23**, but that's only **10.1%** of them.\n\nThe chart shows why raw counts mislead here. The same 23 people in each group mean very different things, because the groups differ in size by almost five times.\n\nThe obvious explanation is that volunteers code after their day job. That's plausible, but the data is a cross-tabulation of 275 people from one project, based on public profiles. Another obvious guess failed in the same data: seniority, measured by commit count or by job title, did *not* predict office-hours work.\n\nUnpaid developers didn't work only on weekends either. The other half were in the average or office-hours clusters.", + "references": "[arXiv: Do Programmers Work at Night or During the Weekend? (Claes, Mäntylä, Kuutila, Adams, ICSE 2018)](https://arxiv.org/abs/1802.05084)", + "images": [ + { + "src": "firefox-paid-unpaid-work-patterns.svg", + "alt": "Two stacked bars showing how Firefox's top developers split across three work-pattern clusters. Unpaid, 48 developers: 47.9% evenings and weekends, 31.3% in between, 20.8% office hours. Paid by Mozilla, 227 developers: 10.1% evenings and weekends, 50.2% in between, 39.7% office hours. A note says both groups had 23 developers in the evenings-and-weekends cluster.", + "caption": "Numbers from Table 3 of Claes, Mäntylä, Kuutila and Adams (2018). Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 440 + } + ] + }, + { + "id": "what-a-commit-timestamp-hides", + "title": "A commit's timestamp says 23:40. Which of these could make it a poor record of when the work happened?", + "type": "multiple-choice", + "options": [ + { + "text": "The developer wrote the code during the day and only committed it late at night", + "isCorrect": true + }, + { + "text": "The commit lost its time zone when it was imported from an older version control system", + "isCorrect": true + }, + { + "text": "A later rebase rewrote the commit's committer date, while its author date kept the original time", + "isCorrect": true + }, + { + "text": "Git stores only UTC, with no offset, so the developer's local hour can't be recovered", + "isCorrect": false + }, + { + "text": "Git records when each line was typed, so there is no gap between writing and committing", + "isCorrect": false + } + ], + "explanation": "A commit timestamp records when someone ran `git commit`, not when they thought, typed or tested. Someone who works all afternoon and commits once before bed looks like a night owl.\n\nGit stores **two** timestamps per commit, each with the time zone offset of the machine that made it. The *author date* is when the change was first written. The *committer date* is when it was last applied, and a rebase, cherry-pick or amend updates it. A study that reads the wrong one can move work by days.\n\nTime zones are the other trap. The offset is only as good as the clock and settings of the machine that made the commit. History imported from older systems may have none at all. Claes and colleagues found exactly that in Apache, so they only used commits from the first one that carried a real time zone.\n\nTry it now: run `git log --format='%ad | %cd'` in a repository you've rebased, and compare the two columns.", + "references": "[Pro Git: Viewing the Commit History (author versus committer)](https://git-scm.com/book/en/v2/Git-Basics-Viewing-the-Commit-History), [arXiv: Do Programmers Work at Night or During the Weekend? (Claes, Mäntylä, Kuutila, Adams, ICSE 2018)](https://arxiv.org/abs/1802.05084)" + }, + { + "id": "swearing-study-sample-size", + "title": "A 2023 bachelor's thesis at KIT asked whether swearing in code goes together with code quality. How large were the two groups it compared?", + "type": "single-choice", + "options": [ + { + "text": "About 3,800 repositories containing swearwords and about 7,600 comparison repositories", + "isCorrect": true + }, + { + "text": "About 380 repositories containing swearwords and about 760 comparison repositories", + "isCorrect": false + }, + { + "text": "38 hand-picked repositories in each group", + "isCorrect": false + }, + { + "text": "Every public repository on GitHub, split by whether it contained a swearword", + "isCorrect": false + } + ], + "explanation": "Jan Strehmel, at the Karlsruhe Institute of Technology, used GitHub's code search to find repositories containing any of more than 300 English swearwords from a published dictionary. A regular expression then re-checked every hit, and repositories where the word wasn't really there were dropped. That produced **about 3,800 \"swear-repos\"**.\n\nThe comparison group was **about 7,600 \"star-repos\"**: repositories with 4 or more stars, found through GitHub's repository search. Keep that detail in mind. The thesis notes that these repositories *can also contain swearwords*, so the comparison is \"swearing repositories versus a general sample\", not \"swearing versus clean\".\n\nThousands of repositories per group is plenty to measure a small difference precisely. The weak points of the study lie elsewhere, as the next Questions show.", + "references": "[KIT: Is there a Correlation between the Use of Swearwords and Code Quality in Open Source Code? (Jan Strehmel, bachelor's thesis, 2023, PDF)](https://cme.h-its.org/exelixis/pubs/JanThesis.pdf)" + }, + { + "id": "swearing-study-language", + "title": "Which programming language was every repository in the swearing study written in?", + "description": "Type the language's name.", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": [ + "C", + "C language", + "the C language", + "C programming language", + "the C programming language" + ] + }, + "explanation": "Every repository was written in **C**. The choice followed from the measuring tool. SoftWipe, built by the same research group, scores C and C++ code, and the thesis kept to C, leaving C++ for future work.\n\nThis limits what the result can tell you. A pattern in C repositories on GitHub, scored by one C-specific tool, says nothing yet about Python, JavaScript or your company's code base. Before you apply a finding to your own work, check that it was measured on something like it.", + "references": "[KIT: Is there a Correlation between the Use of Swearwords and Code Quality in Open Source Code? (Jan Strehmel, bachelor's thesis, 2023, PDF)](https://cme.h-its.org/exelixis/pubs/JanThesis.pdf)" + }, + { + "id": "swearing-softwipe-scores", + "title": "SoftWipe scores code from 0 to 10 for how closely it follows coding standards. What average scores did the two groups get?", + "type": "single-choice", + "options": [ + { + "text": "5.87 for repositories with swearwords, and 5.41 for the comparison group", + "isCorrect": true + }, + { + "text": "5.41 for repositories with swearwords, and 5.87 for the comparison group", + "isCorrect": false + }, + { + "text": "8.9 for repositories with swearwords, and 3.2 for the comparison group", + "isCorrect": false + }, + { + "text": "Both groups averaged 5.6, so there was no difference", + "isCorrect": false + } + ], + "explanation": "The repositories with swearwords scored **5.87** and the comparison group **5.41**. SoftWipe runs a set of static and dynamic analysers, such as the complexity checker Lizard and Clang's AddressSanitizer, and turns their findings into one score from 0 to 10. The study left out compiler warnings, because compiling thousands of unknown projects automatically is hard.\n\nThe difference is **statistically significant**. The 95% confidence intervals, 5.81–5.93 and 5.38–5.45, don't overlap, and Welch's t-test gave a p-value of about 2 × 10⁻⁶¹. With samples this large, chance is not a plausible explanation.\n\nSignificance still isn't **size**. The gap is 0.46 points on a 10-point scale, and the thesis itself warns that a small p-value doesn't mean the result matters in practice. A tiny p-value tells you the difference is probably real. It doesn't tell you the difference is large, or what caused it.", + "references": "[KIT: Is there a Correlation between the Use of Swearwords and Code Quality in Open Source Code? (Jan Strehmel, bachelor's thesis, 2023, PDF)](https://cme.h-its.org/exelixis/pubs/JanThesis.pdf)", + "images": [ + { + "src": "swearing-softwipe-scores.svg", + "alt": "Horizontal bar chart of average SoftWipe score on a 0 to 10 scale. Repositories with swearwords, about 3,800: 5.87, 95% confidence interval 5.81 to 5.93. Comparison repositories with 4 or more stars, about 7,600: 5.41, interval 5.38 to 5.45. A note says the gap is 0.46 points and nobody was assigned to swear.", + "caption": "Numbers from Table 3.1 of Jan Strehmel's bachelor's thesis (KIT, 2023). Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 396 + } + ] + }, + { + "id": "does-swearing-improve-code", + "title": "Which of these are good reasons not to read the thesis as \"swearing makes code better\"?", + "type": "multiple-choice", + "options": [ + { + "text": "It's observational: nobody was assigned to swear, so another difference between the groups could explain the scores", + "isCorrect": true + }, + { + "text": "The comparison group was repositories with 4 or more stars, which could also contain swearwords", + "isCorrect": true + }, + { + "text": "SoftWipe measures how closely code follows coding standards, which stands in for quality rather than measuring it directly", + "isCorrect": true + }, + { + "text": "The difference between the groups wasn't statistically significant", + "isCorrect": false + }, + { + "text": "With only a few dozen repositories per group, the result could be chance", + "isCorrect": false + } + ], + "explanation": "The author makes the point directly: \"Obviously correlation does not imply causation\". Three features of the design explain why.\n\n- **No assignment.** The researcher found repositories that already swore and compared them with others. Anything that differs between the groups could be the real cause. For example, repositories found by searching code for a word may have more comments, or come from people who care about their code enough to get angry at it. These are guesses. The study can't tell them apart, and that's the problem.\n- **A muddy comparison group.** \"4 or more stars\" is not \"no swearing\". Some star-repos probably swore too, and stars bring their own selection.\n- **A proxy outcome.** Coding-standard adherence is measurable and useful, but it isn't the same as correct, maintainable or bug-free code.\n\nThe two wrong reasons are the ones people often reach for. The difference *was* statistically significant, and the samples numbered in the thousands. The problems are all in the design.\n\nThe thesis offers its own hypothesis: swearing may signal emotional involvement with hard code, or relieve stress. It's an idea for a future study. This one didn't test it.", + "references": "[KIT: Is there a Correlation between the Use of Swearwords and Code Quality in Open Source Code? (Jan Strehmel, bachelor's thesis, 2023, PDF)](https://cme.h-its.org/exelixis/pubs/JanThesis.pdf)" + }, + { + "id": "music-study-participants", + "title": "How many software developers took part in Teresa Lesiuk's 2005 field study of music at work?", + "description": "The study ran for five weeks at four Canadian software companies. Type a whole number.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 56 + ] + }, + "explanation": "**56** developers took part: 41 men and 15 women, aged 19 to 55, from four software companies in two Canadian cities. On average they already listened to music for about 1.7 hours a workday, although some never did and some listened nearly all day.\n\nThis is a **field study**. It ran at the developers' real desks on their real projects, not in a lab with puzzle tasks. That makes the results more realistic, but it gives the researcher less control, and the next two Questions show how the design made up for it.\n\n56 people is a modest sample. It's large enough to detect a consistent shift within the same people over time, but too small to say how the effect differs by age, job or music taste.", + "references": "[Psychology of Music: The effect of music listening on work performance (Teresa Lesiuk, 2005)](https://doi.org/10.1177/0305735605050650)" + }, + { + "id": "why-take-the-music-away", + "title": "Lesiuk's developers worked for a week as usual, then two weeks with music, one week without, and one more week with music. Why take the music away in week 4?", + "type": "single-choice", + "options": [ + { + "text": "If scores drop when music is removed and recover when it returns, getting better with practice can't explain the pattern, so music becomes the likely cause", + "isCorrect": true + }, + { + "text": "To give the developers a break from filling in questionnaires", + "isCorrect": false + }, + { + "text": "To run a separate experiment on how much developers dislike silence", + "isCorrect": false + }, + { + "text": "Because comparing week 1 with week 3 had already proved the effect, and week 4 was only a check on the questionnaires", + "isCorrect": false + } + ], + "explanation": "This is an **interrupted time series with removal**. The same people are measured again and again, a condition is switched on, then off, then on again, and the question is whether the measurements follow the switch.\n\nWith only \"before music\" and \"after music\", many things could explain an improvement. People get used to the study, projects move to easier phases, and the weather gets better. All of these trends move in one direction over time. Removing the music tests them. A steady trend can't fall in week 4 and rise again in week 5 exactly when the music comes and goes. If the scores do follow the switch, music is the simplest explanation.\n\nLesiuk also explains why there was no separate control company: each company had its own work culture, so a control group would really compare two cultures. Each developer served as their own control.\n\nThe design can't rule everything out. The developers knew which week was which, and they rated themselves, so expectations could shape the scores. Lesiuk lists this as a challenge of the study.", + "references": "[Psychology of Music: The effect of music listening on work performance (Teresa Lesiuk, 2005)](https://doi.org/10.1177/0305735605050650)", + "images": [ + { + "src": "lesiuk-five-week-design.svg", + "alt": "Timeline of five weekly boxes: week 1 baseline with usual habits, weeks 2 and 3 music on from a library of 65 CDs, week 4 music off and confirmed by a daily signature, week 5 music on again. Below, two dots per week mark measurements of mood, quality of work and time on task, all self-reported. A note says there is no separate control group.", + "caption": "Design from Lesiuk (2005), Psychology of Music 33(2). Diagram drawn for this Quiz.", + "width": 720, + "height": 382 + } + ] + }, + { + "id": "music-study-results", + "title": "Which results did Lesiuk report?", + "type": "multiple-choice", + "options": [ + { + "text": "Self-rated quality of work was lowest in the no-music week and rose again when the music returned", + "isCorrect": true + }, + { + "text": "Tasks overran their intended time the most in the no-music week", + "isCorrect": true + }, + { + "text": "The developers' mood was lowest in the no-music week", + "isCorrect": true + }, + { + "text": "Independent reviewers rated the developers' designs higher in the music weeks", + "isCorrect": false + }, + { + "text": "Heavy metal improved work quality more than classical music did", + "isCorrect": false + } + ], + "explanation": "All three measures moved with the switch.\n\n- **Quality of work** fell to its lowest average in week 4 (11.15 on a scale from 5 to 20) and rose to 11.74 when the music returned. The trend was statistically significant, but Lesiuk calls the changes \"very small\".\n- **Time on task** was worst in week 4. Developers scored each task 0, 5 or 10 for finishing faster than planned, on time or late, and week 4 averaged 6.61. The difference between week 4 and week 5 was the clearest in the study.\n- **Mood** (state positive affect) peaked in week 3 at 27.58 and hit its low in week 4 at 25.50. Within each music week, mood also rose from before listening to after.\n\nThe two wrong options mark the study's limits. Nobody outside rated the work; every number is a **self-report**, so developers who missed their music may also have rated their own work lower. The study didn't compare genres either. Developers picked their own music from a library of 65 CDs or their own collections, so it tested *choosing your own music*, not any particular style.\n\nMany developers already listened to music in the baseline week, which is why the difference between weeks 1 and 3 was small. It's also why removing music in week 4 told the researcher far more than adding it did.", + "references": "[Psychology of Music: The effect of music listening on work performance (Teresa Lesiuk, 2005)](https://doi.org/10.1177/0305735605050650)" + }, + { + "id": "which-study-tested-the-habit", + "title": "Only one of the four studies changed a habit on purpose and measured what happened. Which one?", + "type": "single-choice", + "options": [ + { + "text": "Lesiuk's music study, which removed the music for a week and then brought it back", + "isCorrect": true + }, + { + "text": "The working-hours study by Claes and colleagues, because it covered the most commits", + "isCorrect": false + }, + { + "text": "The swearing thesis, because its p-value was around 10⁻⁶¹", + "isCorrect": false + }, + { + "text": "The Friday study, because it traced each fix back to the change that caused it", + "isCorrect": false + } + ], + "explanation": "Only Lesiuk **intervened**. She switched the music off and on and watched the measures follow, which is what a causal claim needs. The other three observed history, however much of it they had.\n\nHere is how far you can trust each finding.\n\n- **Fridays.** A small gap in one project in 2005, possibly explained by Friday being fix day. It's a hint, not a rule. The 2025 TGIF study found workdays indistinguishable in commit volume.\n- **Night owls.** Solid for what it claims. Commit times from 86 projects are good evidence for *when* commits happen, and most developers keep office hours. It says nothing about the quality of night-time code.\n- **Swearing.** A real and precisely measured correlation, but a small one, with a muddy comparison group and a proxy for quality. It's the weakest case for changing anything.\n- **Music.** The strongest design of the four, but 56 people, self-rated outcomes, small changes, and developers who could choose their own music.\n\nA large sample and a tiny p-value make a pattern hard to dismiss as chance. They can't show what caused it.\n\nIf one of these habits tempts you, run Lesiuk's design on yourself: two weeks with your usual music, one without, then one with again. Keep notes each afternoon, and see whether your own numbers follow the switch.", + "references": "[Psychology of Music: The effect of music listening on work performance (Teresa Lesiuk, 2005)](https://doi.org/10.1177/0305735605050650), [Śliwerski, Zimmermann, Zeller: Don't Program on Fridays! How to Locate Fix-Inducing Changes (2005, PDF)](https://thomas-zimmermann.com/publications/files/sliwerski-wsr-2005.pdf), [arXiv: Do Programmers Work at Night or During the Weekend? (Claes, Mäntylä, Kuutila, Adams, ICSE 2018)](https://arxiv.org/abs/1802.05084), [KIT: Is there a Correlation between the Use of Swearwords and Code Quality in Open Source Code? (Jan Strehmel, bachelor's thesis, 2023, PDF)](https://cme.h-its.org/exelixis/pubs/JanThesis.pdf)" + } + ] +} diff --git a/content/quizzes/programmer-habits-under-the-microscope/eclipse-fix-inducing-by-weekday.svg b/content/quizzes/programmer-habits-under-the-microscope/eclipse-fix-inducing-by-weekday.svg new file mode 100644 index 0000000..9135880 --- /dev/null +++ b/content/quizzes/programmer-habits-under-the-microscope/eclipse-fix-inducing-by-weekday.svg @@ -0,0 +1,93 @@ + + + + + Eclipse: share of changes that later needed a fix + P(bug) by the weekday of the commit, in %. The axis starts at zero. + + 0 + + 2 + + 4 + + 6 + + 8 + + 10 + + 12 + + 14 + + 11.3 + Mon + + 10.4 + Tue + + 11.1 + Wed + + 12.1 + Thu + + 12.2 + Fri + + 11.7 + Sat + + 11.6 + Sun + Highest: Friday 12.2%. Lowest: Tuesday 10.4%. Average across the week: 11.4%. + Numbers from Table 1 of Śliwerski, Zimmermann and Zeller (2005). Data from a single project. + diff --git a/content/quizzes/programmer-habits-under-the-microscope/firefox-paid-unpaid-work-patterns.svg b/content/quizzes/programmer-habits-under-the-microscope/firefox-paid-unpaid-work-patterns.svg new file mode 100644 index 0000000..002adf9 --- /dev/null +++ b/content/quizzes/programmer-habits-under-the-microscope/firefox-paid-unpaid-work-patterns.svg @@ -0,0 +1,80 @@ + + + + + Firefox top developers: who works outside office hours? + Share of each group in each work-pattern cluster (k-means on commit times) + Unpaid + 48 developers + + 47.9% + + 31.3% + + 20.8% + Paid by Mozilla + 227 developers + + 10.1% + + 50.2% + + 39.7% + + Evenings and weekends + + In between + + Office hours (10:00 to 18:00) + Each group had 23 developers in the evenings-and-weekends cluster. + That's about half of the unpaid developers, but only one in ten of the paid ones. + Numbers from Table 3 of Claes, Mäntylä, Kuutila and Adams (2018). + diff --git a/content/quizzes/programmer-habits-under-the-microscope/lesiuk-five-week-design.svg b/content/quizzes/programmer-habits-under-the-microscope/lesiuk-five-week-design.svg new file mode 100644 index 0000000..b777aec --- /dev/null +++ b/content/quizzes/programmer-habits-under-the-microscope/lesiuk-five-week-design.svg @@ -0,0 +1,89 @@ + + + + + Lesiuk (2005): five weeks at work, music switched on and off + 56 developers at four Canadian software companies + + Week 1 + Baseline + usual habits + + Week 2 + Music on + library of 65 CDs + + Week 3 + Music on + listen as you like + + Week 4 + Music off + signed daily + + Week 5 + Music on + library back + + + + + + + + + + + + ● Two measurements a week, in the afternoon, all self-reported: + mood (state positive affect), quality of work, and time on task + No separate control group. Each developer is compared with themselves over time. + Design from Lesiuk (2005), Psychology of Music 33(2). Diagram drawn for this Quiz. + diff --git a/content/quizzes/programmer-habits-under-the-microscope/swearing-softwipe-scores.svg b/content/quizzes/programmer-habits-under-the-microscope/swearing-softwipe-scores.svg new file mode 100644 index 0000000..8254b5e --- /dev/null +++ b/content/quizzes/programmer-habits-under-the-microscope/swearing-softwipe-scores.svg @@ -0,0 +1,89 @@ + + + + + Average SoftWipe score, C repositories on GitHub + Coding-standard adherence from 0 (low) to 10 (high) + + 0 + + 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 + + 10 + With swearwords + ≈3,800 repositories + + 5.87 + 95% CI 5.81 to 5.93 + Comparison: 4+ stars + ≈7,600 repositories + + 5.41 + 95% CI 5.38 to 5.45 + The intervals don't overlap, so the gap is unlikely to be chance. + But the gap is only 0.46 points out of 10, and nobody was assigned to swear. + Numbers from Table 3.1 of Jan Strehmel's bachelor's thesis (KIT, 2023). + diff --git a/content/quizzes/reading-old-paintings.json b/content/quizzes/reading-old-paintings.json new file mode 100644 index 0000000..3fb4687 --- /dev/null +++ b/content/quizzes/reading-old-paintings.json @@ -0,0 +1,963 @@ +{ + "schemaVersion": 1, + "id": "reading-old-paintings", + "title": "Reading old paintings: symbols, perspective and anachronism", + "description": "Paintings made before 1900 were meant to be read. A sleeping dog means fidelity, a lamp that has just gone out means time is up, and a vanishing point tells you where the painter wanted you to stand. Each question shows a real painting or a detail from one and asks what it told the people who first saw it: hidden skulls, saints' attributes, moral jokes, impossible bouquets and Bible stories set in the painter's own street.", + "language": "en", + "tags": [ + "art", + "history", + "culture", + "visual-literacy" + ], + "author": "Quizbun", + "questions": [ + { + "id": "icarus-where-is-he", + "title": "This painting is called *Landscape with the Fall of Icarus*. Where is Icarus?", + "description": "In the myth, Icarus flew too close to the sun on wings of feathers and wax. The wax melted and he fell into the sea. Search the whole scene before you answer.", + "images": [ + { + "src": "icarus-landscape.webp", + "alt": "A wide coastal landscape: in the foreground a ploughman in a red shirt guides a horse along curving furrows, a shepherd with his flock stands behind him, a large sailing ship rides the bay at right, and a low sun sits on the horizon.", + "caption": "After Pieter Bruegel the Elder, *Landscape with the Fall of Icarus*, probably 1560s. Royal Museums of Fine Arts of Belgium, Brussels. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Pieter_Bruegel_the_Elder_-_Landscape_with_the_Fall_of_Icarus_-_Brussels,_Royal_Museums_of_Fine_Arts_of_Belgium_-_Google_Arts_%26_Culture.jpg).", + "width": 1600, + "height": 1044 + }, + { + "src": "icarus-legs-detail.webp", + "alt": "Close-up of the lower right corner: two bare legs kick up out of the green water, with small white feathers scattered on the waves nearby.", + "caption": "Detail: Icarus goes under, legs first.", + "placement": "explanation", + "width": 768, + "height": 615 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Only his legs show, kicking in the water just below the big ship at lower right", + "isCorrect": true + }, + { + "text": "He is still falling, a tiny figure silhouetted against the setting sun", + "isCorrect": false + }, + { + "text": "He is the ploughman in the red shirt, shown after he survived the fall", + "isCorrect": false + }, + { + "text": "He lies washed up on the rocky island in the middle of the bay", + "isCorrect": false + } + ], + "explanation": "Just below the big ship at lower right, a pair of pale legs splashes into the sea, with a few loose feathers drifting down. That is all you see of the painting's title subject.\n\nEveryone else carries on with their day. The ploughman watches his furrow, the shepherd gazes up at the wrong part of the sky, and the angler on the bank minds his line. In Ovid's telling these three look up in astonishment. Here nobody notices. A Flemish proverb says the farmer kept ploughing, meaning that ordinary work goes on while someone dies, and W. H. Auden's poem *Musée des Beaux-Arts* reads the painting the same way.\n\nCheck the sun as well. It is already half-set on the horizon, nowhere near where Icarus fell. Technical studies suggest this canvas is an early copy of a lost Bruegel original, but the museum still treats the composition as his.", + "references": "[Wikipedia: Landscape with the Fall of Icarus](https://en.wikipedia.org/wiki/Landscape_with_the_Fall_of_Icarus)" + }, + { + "id": "ambassadors-anamorphic-skull", + "title": "A long pale smear floats above the floor between the two men in Holbein's *The Ambassadors*. What is it?", + "description": "The panel is a life-size double portrait of two French envoys in London. This detail shows the bottom center.", + "images": [ + { + "src": "ambassadors-floor-smear.webp", + "alt": "A long, pale, diagonal shape streaks across a patterned marble floor between the dark shoes of two standing men. Seen face-on it is hard to read.", + "caption": "Hans Holbein the Younger, *The Ambassadors*, 1533 (detail). National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Hans_Holbein_the_Younger_-_The_Ambassadors_-_Google_Art_Project.jpg).", + "width": 1400, + "height": 623 + }, + { + "src": "ambassadors-skull-corrected.webp", + "alt": "The same detail rotated and compressed along its long axis: the smear becomes a human skull seen from the side, with eye socket, cheekbone and teeth.", + "caption": "The detail rotated about 24 degrees and squeezed to a quarter of its length, roughly what your eye does when you look along the panel from the side.", + "placement": "explanation", + "width": 568, + "height": 520 + }, + { + "src": "ambassadors-full.webp", + "alt": "The whole painting: two richly dressed men stand either side of a two-shelf table crowded with globes, scientific instruments, a lute and books, with the pale diagonal shape on the floor between them.", + "caption": "The whole panel. A small crucifix peeks out from behind the curtain at top left. Hans Holbein the Younger, *The Ambassadors*, 1533. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Hans_Holbein_the_Younger_-_The_Ambassadors_-_Google_Art_Project.jpg).", + "placement": "explanation", + "width": 1400, + "height": 1380 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A skull, stretched so that it only takes shape when you look at the panel from a sharp angle", + "isCorrect": true + }, + { + "text": "A fallen fur stole, painted loosely to contrast with the sharp detail above it", + "isCorrect": false + }, + { + "text": "Old damage that a restorer covered with a neutral patch of paint", + "isCorrect": false + }, + { + "text": "The shadow of a sword held by someone standing outside the picture", + "isCorrect": false + } + ], + "explanation": "It is a human skull, painted with a trick called anamorphosis. Holbein stretched it along a diagonal, so face-on it looks like a smear. Stand close to the wall and look along the panel from high on the right, or low on the left, and the distortion cancels out. A skull appears. The second image imitates that view by rotating the detail and squeezing it along its long axis.\n\nThe skull is a *memento mori*, a reminder of death, placed among fine furs, globes and scientific instruments that show off the two men's wealth and learning. It is also a puzzle, and a boast about Holbein's skill. One theory says the painting hung beside a door or a stair, so a visitor walking past would catch the skull at the right angle. Another says you were meant to view it through a glass tube. Neither has been proved.\n\nThe restoration answer isn't entirely wrong. Parts of the skull had flaked away and were rebuilt in 1997, but the shape itself is Holbein's. He added one more religious sign: a small crucifix, half-hidden behind the green curtain at top left, often read as a hint of hope beside the reminder of death.", + "references": "[National Gallery: The Ambassadors](https://www.nationalgallery.org.uk/paintings/hans-holbein-the-younger-the-ambassadors) · [Wikipedia: Anamorphosis](https://en.wikipedia.org/wiki/Anamorphosis)" + }, + { + "id": "arnolfini-convex-mirror", + "title": "Which of these can you find in or around the convex mirror on the back wall of the Arnolfini Portrait?", + "description": "Jan van Eyck painted this couple in a room in Bruges in 1434. The detail enlarges the mirror, which takes up only a small part of the panel.", + "images": [ + { + "src": "arnolfini-portrait.webp", + "alt": "A man in a dark fur-trimmed cloak and wide black hat holds the hand of a woman in a full green gown in a bedroom. A brass chandelier hangs above them, a small round mirror hangs on the back wall, and a small shaggy dog stands at their feet.", + "caption": "Jan van Eyck, *The Arnolfini Portrait*, 1434. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Van_Eyck_-_Arnolfini_Portrait.jpg).", + "width": 1023, + "height": 1400 + }, + { + "src": "arnolfini-mirror-detail.webp", + "alt": "Close-up of the round convex mirror in a spiked frame set with ten tiny painted roundels. Its curved glass reflects the room in miniature. Ornate script is painted on the wall above it, and a string of amber prayer beads hangs to the left.", + "caption": "Jan van Eyck, *The Arnolfini Portrait*, 1434 (detail). National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Van_Eyck_-_Arnolfini_Portrait.jpg).", + "width": 779, + "height": 900 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The couple, seen from behind", + "isCorrect": true + }, + { + "text": "Two more figures standing in the doorway, facing the couple", + "isCorrect": true + }, + { + "text": "Tiny scenes from the Passion of Christ set into the frame", + "isCorrect": true + }, + { + "text": "A Latin inscription above it: *Johannes de eyck fuit hic 1434*, 'Jan van Eyck was here 1434'", + "isCorrect": true + }, + { + "text": "The painter at his easel, brush raised", + "isCorrect": false + }, + { + "text": "A priest reading the marriage vows", + "isCorrect": false + } + ], + "explanation": "The mirror shows the room from the other side: the backs of the couple and, in the doorway facing them, two tiny visitors, one in blue and one in red. The figure in red is often taken to be van Eyck himself. Unlike Velázquez in *Las Meninas*, he isn't shown painting. There is no priest anywhere in the room. The ten roundels in the frame hold scenes from the Passion, Christ's suffering and death.\n\nAbove the mirror, van Eyck lettered his signature in large script on the wall, the way proverbs were written on walls at the time: 'Jan van Eyck was here, 1434.' In 1934 Erwin Panofsky argued that the two visitors and this eyewitness signature make the painting a record of a wedding with witnesses. Many historians now doubt that. They read it instead as a betrothal, a legal arrangement between husband and wife, or a memorial to a wife who had already died.\n\nThe mirror itself is too good to be real. Nobody in 1434 could make a convex mirror this large.", + "references": "[National Gallery: The Arnolfini Portrait](https://www.nationalgallery.org.uk/paintings/jan-van-eyck-the-arnolfini-portrait) · [Wikipedia: Arnolfini Portrait](https://en.wikipedia.org/wiki/Arnolfini_Portrait)" + }, + { + "id": "arnolfini-not-pregnant", + "title": "Many first-time viewers assume the woman in the Arnolfini Portrait is pregnant. What do art historians usually conclude?", + "images": [ + { + "src": "arnolfini-bride-detail.webp", + "alt": "Detail of the woman: a white linen headdress and a green gown with long fur-lined sleeves and a trailing skirt. One hand rests on the front of the skirt, which is gathered up and bulges forward below a high belt.", + "caption": "Notice how much cloth is gathered up under her hand. Jan van Eyck, *The Arnolfini Portrait*, 1434 (detail). National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Van_Eyck_-_Arnolfini_Portrait.jpg).", + "width": 528, + "height": 1300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Probably not: she is holding up the heavy folds of a very full, fashionable gown", + "isCorrect": true + }, + { + "text": "Clearly pregnant: the painting was commissioned to announce an heir", + "isCorrect": false + }, + { + "text": "Pregnant, which is why a bed is in the room: it is a birthing chamber", + "isCorrect": false + }, + { + "text": "Wearing a padded costume for a religious play", + "isCorrect": false + } + ], + "explanation": "The rounded shape comes from the dress. Fashionable gowns of the 1430s had a high waist and a huge skirt, and a woman gathered the front up to walk. Van Eyck and his contemporaries painted virgin saints with exactly the same swelling silhouette. The cloth itself was a boast: the more fabric you wore, the richer you looked, and Arnolfini was a merchant in the cloth trade.\n\nNeither woman proposed as the sitter is known to have had children. The bed isn't evidence either, because wealthy households used beds in reception rooms as seating.\n\nThe question isn't completely closed. One recent theory reads the painting as a memorial to a wife who had died, possibly in childbirth. Others read her gesture as a hope for children rather than a sign that she is expecting.", + "references": "[Wikipedia: Arnolfini Portrait](https://en.wikipedia.org/wiki/Arnolfini_Portrait)" + }, + { + "id": "vanitas-death-and-time", + "title": "In Steenwyck's still life, which objects stand for death and passing time, and not for the pleasures that death cuts short?", + "description": "Harmen Steenwyck painted this in Leiden around 1640. His buyers knew how to read every object on the table.", + "images": [ + { + "src": "steenwyck-vanitas.webp", + "alt": "A table-top still life lit by a diagonal shaft of light: a skull lying on its side, a brass oil lamp with a wisp of smoke, a lute, a recorder and a long wind instrument, stacked books, a sword in a black sheath, a small round case, a stoneware jug and a large spiral shell.", + "caption": "Harmen Steenwyck, *Still Life: An Allegory of the Vanities of Human Life*, about 1640. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Harmen_Steenwijck_-_Vanitas_Still-Life_-_WGA21768.jpg).", + "width": 1500, + "height": 1179 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The skull", + "isCorrect": true + }, + { + "text": "The brass oil lamp, just blown out and still smoking", + "isCorrect": true + }, + { + "text": "The small round watch case lying beside the sword", + "isCorrect": true + }, + { + "text": "The lute, the recorder and the shawm", + "isCorrect": false + }, + { + "text": "The books", + "isCorrect": false + }, + { + "text": "The Japanese sword in its lacquered sheath", + "isCorrect": false + } + ], + "explanation": "This is a *vanitas*, named after Ecclesiastes: 'Vanity of vanities, all is vanity.' The objects fall into two groups.\n\nThe first group shows what people spend their lives on. The books stand for learning. The lute, recorder and shawm, along with the wine flask, stand for pleasure. The costly Japanese sword stands for wealth and power.\n\nThe second group says that all of it ends. The skull sits in the shaft of light. The lamp has just gone out, with a thread of smoke still rising, and the watch keeps ticking.\n\nThe tropical shell does double duty. It was an expensive collector's piece, and, like the skull, an empty home whose occupant had died. Many Dutch still lifes can be read the same way: list the pleasures, then find the reminder.", + "references": "[National Gallery: An Allegory of the Vanities of Human Life](https://www.nationalgallery.org.uk/paintings/harmen-steenwyck-still-life-an-allegory-of-the-vanities-of-human-life) · [Wikipedia: Vanitas](https://en.wikipedia.org/wiki/Vanitas)" + }, + { + "id": "chardin-soap-bubble", + "title": "In the Dutch paintings Chardin learned from, a soap bubble was a stock symbol. What did it usually stand for?", + "description": "Jean Siméon Chardin painted several versions of this scene in the 1730s.", + "images": [ + { + "src": "chardin-soap-bubbles.webp", + "alt": "A young man leans out of a stone window opening and blows through a straw, swelling a large, clear bubble. A small child peeks over the ledge beside him, and a glass of soapy water stands on the sill.", + "caption": "Jean Siméon Chardin, *Soap Bubbles*, probably 1733–34. National Gallery of Art, Washington. Public domain (CC0), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Jean_Sim%C3%A9on_Chardin,_Soap_Bubbles,_probably_1733-1734,_NGA_994.jpg).", + "width": 1053, + "height": 1300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The shortness and fragility of human life", + "isCorrect": true + }, + { + "text": "Cleanliness and good housekeeping", + "isCorrect": false + }, + { + "text": "Idle wealth, because soap was an expensive import", + "isCorrect": false + }, + { + "text": "The new science of light, following Newton's work on colors in thin films", + "isCorrect": false + } + ], + "explanation": "The Latin saying *homo bulla*, 'man is a bubble', goes back to the Roman writer Varro, and Erasmus made it popular. A bubble floats, shines for a moment and bursts. That made it a favorite prop in vanitas still lifes, and children blow bubbles in many seventeenth-century Dutch paintings. Chardin knew that tradition well.\n\nWhether he meant the full sermon is less certain. Some writers think he simply liked painting a quiet moment of play, and the painting never spells out a moral. Still, a viewer in the 1730s would have recognized the old symbol: a young man leaning over a sill, blowing a bubble that is about to pop.", + "references": "[Wikipedia: Soap Bubbles (Chardin)](https://en.wikipedia.org/wiki/Soap_Bubbles_(Chardin)) · [Wikipedia: Vanitas](https://en.wikipedia.org/wiki/Vanitas)" + }, + { + "id": "gold-ground-reasons", + "title": "Which of these help explain why Simone Martini set this Annunciation against solid gold instead of a sky or a room?", + "description": "Painted in 1333 for the cathedral of Siena.", + "images": [ + { + "src": "simone-martini-annunciation.webp", + "alt": "A wide gilded altarpiece with pointed Gothic arches. At the center an angel kneels, holding an olive branch, before a woman in dark blue who shrinks back on her throne, with a vase of lilies between them. A standing saint fills each side panel, and everything sits against flat gold.", + "caption": "Simone Martini and Lippo Memmi, *Annunciation with Saint Margaret and Saint Ansanus*, 1333. Uffizi, Florence. Public domain (CC0), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Simone_Martini_%E2%80%94_Annunciation_with_St._Margaret_and_St._Ansanus.jpg).", + "width": 1500, + "height": 1302 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "Gold stood for heavenly light, lifting the scene out of ordinary space and time", + "isCorrect": true + }, + { + "text": "In a candlelit church, the gold leaf flickered and glowed as the flames moved", + "isCorrect": true + }, + { + "text": "Gold leaf was costly, so it honored the holy figures and showed what the patrons had spent", + "isCorrect": true + }, + { + "text": "Painters of the time had not yet learned how to paint a convincing sky", + "isCorrect": false + } + ], + "explanation": "The gold was a choice. Some thirty years earlier, Giotto had painted deep blue skies across the Arena Chapel in Padua, and Simone could paint a sky too.\n\nGold did things a sky couldn't. It set Gabriel and Mary in a timeless, heavenly space. In a dim cathedral lit by candles, the burnished leaf caught every flame and seemed to give off light of its own. It also displayed money and devotion. Patrons went on writing gold into contracts long after painters had mastered landscape: in 1445, the confraternity that commissioned Piero della Francesca's Misericordia altarpiece still required a gilded background.\n\nLook at the greeting that runs from the angel's mouth toward Mary: *Ave gratia plena dominus tecum*, 'Hail, full of grace, the Lord is with you.' The letters are raised and gilded, part of the gold surface itself.", + "references": "[Wikipedia: Gold ground](https://en.wikipedia.org/wiki/Gold_ground) · [Wikipedia: Annunciation with Saint Margaret and Saint Ansanus](https://en.wikipedia.org/wiki/Annunciation_with_Saint_Margaret_and_Saint_Ansanus)" + }, + { + "id": "misericordia-hierarchical-scale", + "title": "Why are the kneeling people in Piero della Francesca's *Madonna of Mercy* so much smaller than the Virgin who shelters them?", + "images": [ + { + "src": "piero-misericordia.webp", + "alt": "Against a gold background, a very tall Virgin in a red dress spreads her dark blue cloak wide with both arms. Eight much smaller men and women kneel in prayer beneath it, one of them wearing a black hood with eyeholes.", + "caption": "Piero della Francesca, *Madonna of Mercy*, central panel of the Misericordia Polyptych, 1445–62. Museo Civico, Sansepolcro. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Piero_della_Francesca_-_Polyptych_of_the_Misericordia_-_Madonna_of_Mercy_-_WGA17449.jpg).", + "width": 900, + "height": 1341 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Size shows importance: the holiest figure is painted largest, whatever the distance", + "isCorrect": true + }, + { + "text": "They kneel further back in space, so perspective makes them smaller", + "isCorrect": false + }, + { + "text": "They are children from an orphanage the confraternity ran", + "isCorrect": false + }, + { + "text": "Piero ran out of room on the panel and squeezed them in", + "isCorrect": false + } + ], + "explanation": "This is hierarchical scale: size shows rank, not distance. The kneeling figures are members of the confraternity that commissioned the altarpiece, one of them wearing the hood members wore in processions. They are as close to us as Mary is, and her cloak curves around them like the apse of a church.\n\nPiero was one of the great theorists of perspective and gave every figure a solid, three-dimensional body. Here he still followed the older rule, and the Virgin's importance sets her size. Compare Masaccio's *Trinity* in Florence, painted a generation earlier. There the donors kneel at the same scale as the saints, which was new at the time.", + "references": "[Wikipedia: Hierarchical proportion](https://en.wikipedia.org/wiki/Hierarchical_proportion) · [Wikipedia: Polyptych of the Misericordia](https://en.wikipedia.org/wiki/Polyptych_of_the_Misericordia)" + }, + { + "id": "medieval-baby-small-adult", + "title": "Why does the Christ Child in Berlinghiero's thirteenth-century *Madonna and Child* look like a small, serious adult?", + "images": [ + { + "src": "berlinghiero-madonna.webp", + "alt": "A gold-ground panel: the Virgin in a dark blue mantle holds on her arm a child in an orange robe whose face, proportions and expression look like those of a grown man.", + "caption": "Berlinghiero, *Madonna and Child*, 1230s. Metropolitan Museum of Art, New York. Public domain (CC0), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Madonna_and_Child_MET_DT5279.jpg).", + "width": 879, + "height": 1300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "On purpose: he was shown as the all-wise Word of God in a small body, not as an ordinary baby", + "isCorrect": true + }, + { + "text": "Medieval painters rarely saw infants, who were kept out of sight", + "isCorrect": false + }, + { + "text": "Tight swaddling was believed to make babies mature faster, so painters showed them older", + "isCorrect": false + }, + { + "text": "Later restorers repainted the face in an adult style", + "isCorrect": false + } + ], + "explanation": "Art historians usually explain it through theology. Christ was believed to be fully God from birth, complete in wisdom, so painters gave him a grown man's proportions and face, like a small adult seated on his mother's arm. The formula came from Byzantine icons, and Berlinghiero, working in Lucca, followed it closely: the gold ground, Mary's hand gesturing toward her son, the flat patterned drapery.\n\nThese painters weren't trying to paint a real baby. Babies began to look like babies in fifteenth-century Italy, as painters studied real bodies and filled pictures with chubby infants and cherubs.", + "references": "[Wikipedia: Madonna (art)](https://en.wikipedia.org/wiki/Madonna_(art))" + }, + { + "id": "virgin-blue-ultramarine", + "title": "Why is the Virgin's mantle so often an intense blue, as in Sassoferrato's *Virgin in Prayer*?", + "images": [ + { + "src": "sassoferrato-virgin-in-prayer.webp", + "alt": "Half-length figure of a young woman with bowed head and hands pressed together in prayer, a white veil over her hair, wrapped in a deep, brilliant blue mantle against a plain dark background.", + "caption": "Sassoferrato, *The Virgin in Prayer*, 1640–50. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Sassoferrato_-_Jungfrun_i_b%C3%B6n.jpg).", + "width": 1034, + "height": 1300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The finest blue, ultramarine, was made from lapis lazuli and could cost as much as gold, so using it honored her", + "isCorrect": true + }, + { + "text": "Blue was the cheapest pigment, which suited a humble woman", + "isCorrect": false + }, + { + "text": "The Gospels describe Mary wearing a blue cloak", + "isCorrect": false + }, + { + "text": "Blue had no special meaning; it was chosen to balance the red and white", + "isCorrect": false + } + ], + "explanation": "The blue is ultramarine, made from lapis lazuli, a stone mined in north-eastern Afghanistan and brought to Europe, probably through Venice. Turning the stone into pigment took long grinding, sifting and washing, and the best ultramarine could be worth as much as gold. Painters' contracts sometimes specified the grade of blue to be used for the Virgin.\n\nGiving her the most precious color was an act of devotion, and over time the blue also came to stand for her purity and status. The Gospels never describe her clothes.\n\nSassoferrato, painting around 1640–50, made the most of it. Against the plain dark background, the ultramarine blazes. It stayed expensive until a synthetic version arrived in the 1820s.", + "references": "[National Gallery: The Virgin in Prayer](https://www.nationalgallery.org.uk/paintings/sassoferrato-the-virgin-in-prayer) · [Wikipedia: Ultramarine](https://en.wikipedia.org/wiki/Ultramarine)" + }, + { + "id": "last-supper-vanishing-point", + "title": "In Leonardo's *Last Supper*, the ceiling beams and the tops of the wall hangings all recede toward one point. Where is it?", + "images": [ + { + "src": "last-supper.webp", + "alt": "A long wall painting: thirteen men sit along one side of a table covered with a white cloth, in a room with a coffered ceiling, dark hangings receding along both side walls, and three bright windows in the back wall. An arched doorway cuts into the bottom center of the painting.", + "caption": "Leonardo da Vinci, *The Last Supper*, about 1495–98. Santa Maria delle Grazie, Milan. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Leonardo_da_Vinci_-_The_Last_Supper_high_res.jpg).", + "width": 1600, + "height": 870 + }, + { + "src": "last-supper-vanishing-point.webp", + "alt": "The same painting with yellow lines drawn along the tops of the hangings on both side walls and along two ceiling beams. All four lines meet in a dot on Christ's head.", + "caption": "Four receding edges, extended until they meet.", + "placement": "explanation", + "width": 1600, + "height": 870 + } + ], + "type": "single-choice", + "options": [ + { + "text": "At Christ's head, in the center of the picture", + "isCorrect": true + }, + { + "text": "At the window behind the apostles on the far right", + "isCorrect": false + }, + { + "text": "At Judas's hand as it reaches for the bread", + "isCorrect": false + }, + { + "text": "Below the table, at the arched doorway", + "isCorrect": false + } + ], + "explanation": "The receding lines of the room, from the coffered ceiling beams to the tops of the hangings on both walls, converge on Christ's head, near his right temple. Wherever your eye enters the picture, the lines lead it there. The bright window behind him frames his head like a halo, without any gold.\n\nThere is a small hole in the plaster at that point. It probably held a nail, and Leonardo stretched strings from it to mark out the perspective lines on the wall.\n\nThe arched gap at the bottom isn't part of the design. Someone cut a doorway through the wall in 1652, and it destroyed Christ's feet.", + "references": "[Wikipedia: The Last Supper (Leonardo)](https://en.wikipedia.org/wiki/The_Last_Supper_(Leonardo))" + }, + { + "id": "trinity-eye-level", + "title": "Masaccio painted this chapel onto a flat church wall around 1425–28. Where did he place the vanishing point, and what did that do for a visitor?", + "images": [ + { + "src": "masaccio-trinity.webp", + "alt": "A tall fresco of a painted classical chapel. Fluted pilasters and a round arch frame a coffered barrel vault. Inside, Christ hangs on the cross with God the Father standing behind him and Mary and John on either side. Two donors, in red and in dark blue, kneel in profile outside the arch.", + "caption": "Masaccio, *Holy Trinity*, about 1425–28. Santa Maria Novella, Florence. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Masaccio,_Holy_Trinity,_1425-28,_Santa_Maria_Novella,_Florence.jpg).", + "width": 868, + "height": 1400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Low, at about the eye level of an adult standing in the church, so the vault seems to open upward into a real chapel beyond the wall", + "isCorrect": true + }, + { + "text": "High, at God the Father's head, so visitors had to kneel to see the scene correctly", + "isCorrect": false + }, + { + "text": "At the center of the cross, so the scene looks the same from anywhere in the church", + "isCorrect": false + }, + { + "text": "Far to one side, toward the church door, so the illusion only worked as you walked in", + "isCorrect": false + } + ], + "explanation": "Masaccio set the horizon low, on the ledge between the two kneeling donors, roughly where an adult standing in front of the wall would be looking. From there the coffered vault recedes upward and back, and you look up into it as you would into a real side chapel. Vasari wrote that it seems as if there is a hole in the wall.\n\nThe donors kneel outside the painted arch, as if in the church beside you, and they are the same size as the holy figures, which was new in the 1420s.\n\nBelow them, cropped out of this photo, a painted tomb holds a skeleton with a warning: 'I was once what you are, and what I am you will also be.' The fresco climbs from that reminder of death, past the donors, up to God.", + "references": "[Wikipedia: Holy Trinity (Masaccio)](https://en.wikipedia.org/wiki/Holy_Trinity_(Masaccio))" + }, + { + "id": "catherine-wheel", + "title": "Which saint did Raphael paint here?", + "description": "Saints are identified by their attributes, objects taken from their stories. This one leans on hers.", + "images": [ + { + "src": "raphael-saint-catherine.webp", + "alt": "A young woman in a blue dress with green sleeves and a crimson mantle looks up at the sky with one hand on her chest. Her other arm rests on a large wooden wheel, and a calm river landscape stretches behind her.", + "caption": "Raphael, *Saint Catherine of Alexandria*, about 1507. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Raffael_020.jpg).", + "width": 998, + "height": 1300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Catherine of Alexandria, with the spiked wheel on which she was to be tortured", + "isCorrect": true + }, + { + "text": "Mary Magdalene, with the wheel of fortune she turned her back on", + "isCorrect": false + }, + { + "text": "Barbara, with the mill wheel of the tower where her father imprisoned her", + "isCorrect": false + }, + { + "text": "Agnes, with a cartwheel from the procession of her martyrdom", + "isCorrect": false + } + ], + "explanation": "The wheel identifies her. According to legend, Catherine was a fourth-century princess of Alexandria who refused to give up her faith. The emperor Maxentius ordered her broken on a spiked wheel, but a thunderbolt shattered it before it could harm her, and she was beheaded instead.\n\nPainters often add a sword and a martyr's palm. Raphael left those out and showed her looking up toward a golden break in the clouds, hand on heart. The wheel alone was enough for a viewer around 1507, just as keys meant Peter and a lamb meant Agnes. Her wheel also gave its name to a spinning firework, the Catherine wheel.\n\nThe other options borrow real saints but not their attributes: Mary Magdalene carries a jar of ointment and Barbara a tower.", + "references": "[National Gallery: Saint Catherine of Alexandria](https://www.nationalgallery.org.uk/paintings/raphael-saint-catherine-of-alexandria) · [Wikipedia: Catherine of Alexandria](https://en.wikipedia.org/wiki/Catherine_of_Alexandria)" + }, + { + "id": "lucy-eyes-on-a-stem", + "title": "Francesco del Cossa's saint holds up a slender stem with something odd at its tip. What is it, and why?", + "images": [ + { + "src": "del-cossa-saint-lucy.webp", + "alt": "A woman against a gold ground holds a palm frond in one hand. In the other she delicately raises a thin stalk topped by a small, curious two-part bloom.", + "caption": "Francesco del Cossa, *Saint Lucy*, about 1473–74. National Gallery of Art, Washington. Public domain (CC0), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Francesco_del_Cossa,_Saint_Lucy,_c._1473-1474,_NGA_369.jpg).", + "width": 913, + "height": 1300 + }, + { + "src": "del-cossa-lucy-hand-detail.webp", + "alt": "Close-up of her hand: the thin stalk forks into two curling shoots, each ending in a realistic human eye.", + "caption": "Francesco del Cossa, *Saint Lucy*, about 1473–74 (detail). National Gallery of Art, Washington. Public domain (CC0), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Francesco_del_Cossa,_Saint_Lucy,_c._1473-1474,_NGA_369.jpg).", + "placement": "explanation", + "width": 470, + "height": 440 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A pair of human eyes, because Lucy is the protector of sight and legend says her eyes were torn out", + "isCorrect": true + }, + { + "text": "Poppy seed heads, a symbol of the sleep of death", + "isCorrect": false + }, + { + "text": "Two pearls, a sign of her noble birth", + "isCorrect": false + }, + { + "text": "Olive buds, for peace between the church and the empire", + "isCorrect": false + } + ], + "explanation": "Two human eyes sprout from the stem like a flower. She is Saint Lucy of Syracuse, martyred in 304. Her name, Lucia, shares a root with Latin *lux*, light, and devotion to her as a protector of sight probably gave her this emblem.\n\nStories from the fifteenth century on say her eyes were gouged out or that she removed them herself. Painters usually put the eyes on a dish, and del Cossa turned them into a strange, elegant bloom. The palm in her other hand is the standard sign of a martyr. She is still the patron saint of the blind.", + "references": "[Wikipedia: Saint Lucy](https://en.wikipedia.org/wiki/Saint_Lucy)" + }, + { + "id": "jerome-attributes", + "title": "Which details in Antonello da Messina's painting identify the scholar at the desk as Saint Jerome?", + "images": [ + { + "src": "antonello-saint-jerome.webp", + "alt": "A scholar in red robes reads at a wooden desk on a raised platform inside a vast vaulted hall, seen through a stone arch. A peacock and a partridge stand on the front ledge, and a shadowy animal walks along the colonnade at right.", + "caption": "Antonello da Messina, *Saint Jerome in his Study*, about 1475. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Antonello_da_Messina_-_St_Jerome_in_his_study_-_National_Gallery_London.jpg).", + "width": 1104, + "height": 1400 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The lion padding through the shadows of the hall at right", + "isCorrect": true + }, + { + "text": "His red cardinal's robes", + "isCorrect": true + }, + { + "text": "The peacock on the front ledge", + "isCorrect": false + }, + { + "text": "The partridge on the front ledge", + "isCorrect": false + } + ], + "explanation": "Jerome, the fourth-century scholar who translated the Bible into Latin, has two standard attributes.\n\nThe first is a lion. Legend says he pulled a thorn from a lion's paw in the desert and it stayed with him. Here it walks down the aisle at right.\n\nThe second is a cardinal's red robes, often with a wide red hat. That is itself an anachronism: there were no cardinals in Jerome's day, but later centuries gave him the rank as an honor.\n\nThe birds on the ledge carry meanings of their own but could appear with anyone. The peacock stood for immortality, because its flesh was thought never to decay. The partridge stood for truth, because it was believed to recognize its mother's call.", + "references": "[National Gallery: Saint Jerome in his Study](https://www.nationalgallery.org.uk/paintings/antonello-da-messina-saint-jerome-in-his-study) · [Wikipedia: Jerome](https://en.wikipedia.org/wiki/Jerome)" + }, + { + "id": "crivelli-annunciation-symbols", + "title": "Which of these pairings of object and meaning fit Crivelli's *Annunciation*?", + "description": "Painted in 1486 for a friary church in Ascoli Piceno, in central Italy.", + "images": [ + { + "src": "crivelli-annunciation.webp", + "alt": "A tall panel of a sunlit Renaissance street in steep perspective. At left an angel holding a white lily kneels beside a bishop who carries a model of a town. At right, through an open wall, a young woman kneels at a lectern. A thin golden beam slants from a glowing opening in the sky into her room, a peacock perches on the balcony above, and an apple and a cucumber lie on the front ledge.", + "caption": "Carlo Crivelli, *The Annunciation, with Saint Emidius*, 1486. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:The_Annunciation,_with_Saint_Emidius_-_Carlo_Crivelli_-_National_Gallery.jpg).", + "width": 993, + "height": 1400 + }, + { + "src": "crivelli-apple-cucumber-detail.webp", + "alt": "Detail of the front ledge: a green cucumber and a red-and-yellow apple lying on pale stone above a lettered inscription.", + "caption": "Carlo Crivelli, *The Annunciation, with Saint Emidius*, 1486 (detail). National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:The_Annunciation,_with_Saint_Emidius_-_Carlo_Crivelli_-_National_Gallery.jpg).", + "width": 845, + "height": 380 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The apple on the ledge: the forbidden fruit of the Fall", + "isCorrect": true + }, + { + "text": "The cucumber beside it: the promise of resurrection", + "isCorrect": true + }, + { + "text": "The peacock on the balcony: immortality", + "isCorrect": true + }, + { + "text": "The lily Gabriel carries: Mary's purity", + "isCorrect": true + }, + { + "text": "The golden beam from the sky: the Easter sunrise", + "isCorrect": false + }, + { + "text": "The dove traveling down the beam: peace between Ascoli and the pope", + "isCorrect": false + } + ], + "explanation": "Crivelli packs the scene with signs. The apple and the cucumber on the front ledge form a pair. The apple recalls the fall of Adam and Eve. The cucumber, or gourd, stands for resurrection and redemption, the undoing of that fall through the child Mary is about to conceive. The peacock meant immortality, because people believed its flesh never decayed. Gabriel's white lily marks Mary's purity.\n\nThe golden beam is the moment of conception itself. It shoots from a burst of light in the sky, passes through a neat opening in the wall and reaches Mary, carrying the dove of the Holy Spirit. So the dove isn't a political emblem, even though the painting does carry a civic message about Ascoli.", + "references": "[National Gallery: The Annunciation, with Saint Emidius](https://www.nationalgallery.org.uk/paintings/carlo-crivelli-the-annunciation-with-saint-emidius) · [Wikipedia: The Annunciation, with Saint Emidius](https://en.wikipedia.org/wiki/The_Annunciation,_with_Saint_Emidius)" + }, + { + "id": "crivelli-ascoli-anachronism", + "title": "Why has Crivelli moved the Annunciation to a busy fifteenth-century Italian street, with a local bishop showing Gabriel a model of a town?", + "description": "The bishop is Saint Emidius, patron saint of Ascoli Piceno, the town the altarpiece was made for. Along the bottom of the panel runs the inscription LIBERTAS ECCLESIASTICA.", + "images": [ + { + "src": "crivelli-emidius-detail.webp", + "alt": "Detail: a winged angel in gold brocade kneels in the street holding a lily, while a bishop in a mitre beside him holds out a detailed model of a walled town with towers.", + "caption": "Saint Emidius interrupts Gabriel to show him the town. Carlo Crivelli, *The Annunciation, with Saint Emidius*, 1486 (detail). National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:The_Annunciation,_with_Saint_Emidius_-_Carlo_Crivelli_-_National_Gallery.jpg).", + "width": 1061, + "height": 1100 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Ascoli won a measure of self-government from the pope in 1482, and the news arrived on 25 March, the feast of the Annunciation, so the painting celebrates both", + "isCorrect": true + }, + { + "text": "Nobody in Italy knew what Nazareth looked like, so painters had no alternative", + "isCorrect": false + }, + { + "text": "The friars claimed the Annunciation had really taken place in Ascoli", + "isCorrect": false + }, + { + "text": "Church rules of the time required every altarpiece to include the local patron saint", + "isCorrect": false + } + ], + "explanation": "Ascoli was ruled by the pope. In 1482 Pope Sixtus IV granted it a degree of self-government, and the news reached the town on 25 March, the feast of the Annunciation. From then on, the feast was celebrated with a great procession to the friary this altarpiece was made for.\n\nCrivelli ties the sacred story to the civic one. Emidius holds a model of Ascoli, whose towers can still be recognized. The coats of arms along the base belong to the pope, the town and its bishop. LIBERTAS ECCLESIASTICA, 'church liberty', was the title of the papal document granting Ascoli its rights. On the bridge behind, a man reads a message that has just arrived by carrier pigeon. It may be the news of that document, and it echoes Gabriel's own delivery.\n\nPainters routinely set Bible stories in their own streets and clothes. Here the local setting is the point.", + "references": "[National Gallery: The Annunciation, with Saint Emidius](https://www.nationalgallery.org.uk/paintings/carlo-crivelli-the-annunciation-with-saint-emidius)" + }, + { + "id": "venus-dog-fidelity", + "title": "A small dog sleeps curled at the feet of Titian's *Venus of Urbino*. What did such a dog usually signify in Renaissance painting?", + "images": [ + { + "src": "titian-venus-of-urbino.webp", + "alt": "A nude woman reclines on white sheets, holding a small posy of roses and looking directly at the viewer. A small dog sleeps curled at her feet, and behind her two maids kneel at a large chest in a palace room.", + "caption": "Titian, *Venus of Urbino*, 1538. Uffizi, Florence. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Tiziano_-_Venere_di_Urbino_-_Google_Art_Project.jpg).", + "width": 1600, + "height": 1125 + }, + { + "src": "manet-olympia.webp", + "alt": "Manet's version: a nude woman reclines on a white bed in the same pose, looking straight out. A maid brings her a large bouquet, and at her feet a black cat stands with its back arched.", + "caption": "Manet's answer, 330 years later. Édouard Manet, *Olympia*, 1863. Musée d'Orsay, Paris. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Edouard_Manet_-_Olympia_-_Google_Art_Project_3.jpg).", + "placement": "explanation", + "width": 1600, + "height": 1090 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Fidelity, especially within marriage", + "isCorrect": true + }, + { + "text": "The pleasures of the hunt and of country life", + "isCorrect": false + }, + { + "text": "The approach of death, since dogs guided souls to the underworld", + "isCorrect": false + }, + { + "text": "Nothing in particular: Titian painted his own pet into many pictures", + "isCorrect": false + } + ], + "explanation": "A dog, and especially a small, sleeping lapdog, was a common sign of fidelity. The Latin *fido* means 'I trust'. That is one reason some historians read this 1538 painting as a celebration of marriage. In the background two maids search a *cassone*, the kind of chest that held a bride's clothes. Others think the woman is a courtesan, and the debate isn't settled.\n\nThe dog's meaning is clearest in what Manet did with it. His *Olympia* of 1863 borrows Titian's composition, but where the sleeping dog was, a black cat stands with its back arched. Visitors to the 1865 Paris Salon took the swap, together with her name and her direct gaze, as signs that she was a prostitute, and the painting caused a scandal.", + "references": "[Wikipedia: Venus of Urbino](https://en.wikipedia.org/wiki/Venus_of_Urbino) · [Wikipedia: Olympia (Manet)](https://en.wikipedia.org/wiki/Olympia_(Manet))" + }, + { + "id": "bosschaert-impossible-bouquet", + "title": "Why could this bouquet by Ambrosius Bosschaert never have stood on a real table?", + "description": "Painted in 1621. Look at which flowers share the vase.", + "images": [ + { + "src": "bosschaert-bouquet.webp", + "alt": "A dense bouquet in a round glass vase on a stone ledge: striped tulips, a yellow iris, roses, a peony and many smaller flowers, every bloom fully open, with a butterfly and a fallen cyclamen on the ledge and a painted inscription below.", + "caption": "Ambrosius Bosschaert the Elder, *Bouquet of Flowers in a Glass Vase*, 1621. National Gallery of Art, Washington. Public domain (CC0), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Ambrosius_Bosschaert,_Bouquet_of_Flowers_in_a_Glass_Vase,_1621,_NGA_94743.jpg).", + "width": 887, + "height": 1300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The flowers bloom at different times of year; the painter combined studies made over many months", + "isCorrect": true + }, + { + "text": "Tulips had not yet reached the Netherlands in 1621", + "isCorrect": false + }, + { + "text": "Glass vases like this one could not be made until the nineteenth century", + "isCorrect": false + }, + { + "text": "Several of the flowers are invented species that never existed", + "isCorrect": false + } + ], + "explanation": "Early Dutch flower pieces are collages. Bosschaert painted individual blooms from life as each came into flower, spring tulips and irises, early-summer roses and so on, then combined the studies into one ideal bouquet with every flower at its peak and none drooping. No single week of the year could have supplied this vase.\n\nThe tulips were real enough. They had been grown in Dutch gardens since the 1590s, and striped ones were among the most prized flowers of the day, years before the tulip mania of 1636–37.\n\nA viewer in 1621 would have admired the impossible abundance and the skill. Many would also have seen a quieter theme: the butterfly and the fallen cyclamen on the ledge are reminders that all this beauty will fade.", + "references": "[Wikipedia: Ambrosius Bosschaert](https://en.wikipedia.org/wiki/Ambrosius_Bosschaert) · [Wikipedia: Tulip mania](https://en.wikipedia.org/wiki/Tulip_mania)" + }, + { + "id": "vermeer-love-letter-seascape", + "title": "In Vermeer's *The Love Letter*, a seascape with a sailing ship hangs on the wall behind the two women. What does it add?", + "images": [ + { + "src": "vermeer-love-letter.webp", + "alt": "View through a dark doorway into a bright room: a seated woman in a yellow satin jacket, holding a stringed instrument, looks up at a standing maid who has just handed her a letter. Two paintings hang behind them, a landscape and a seascape with a sailing ship, and a pair of slippers and a broom lie in the foreground.", + "caption": "Johannes Vermeer, *The Love Letter*, about 1669–70. Rijksmuseum, Amsterdam. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Johannes_Vermeer_-_%27De_liefdesbrief%27_-_Google_Art_Project.jpg).", + "width": 1121, + "height": 1300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A comment on the letter: love was often compared to a voyage at sea, so the painting hints at how the affair is going", + "isCorrect": true + }, + { + "text": "A record of the family business, since the absent husband was a ship's captain", + "isCorrect": false + }, + { + "text": "Nothing in particular: Dutch walls were simply crowded with paintings", + "isCorrect": false + }, + { + "text": "News of a death at sea, which the letter is bringing", + "isCorrect": false + } + ], + "explanation": "Dutch viewers were used to pictures within pictures that comment on the main scene. Love as a sea voyage was a familiar image in emblem books and poems: the lover as a ship at the mercy of the weather, with calm water for a happy affair and storms for trouble. This sea looks rough, which suggests a stormy romance.\n\nVermeer adds other clues. The woman holds a cittern, and music-making was a standard sign of love. Her slippers lie kicked off in the foreground and the broom leans forgotten, so the housework has been abandoned. The landscape above the seascape shows a traveler on a sandy road, which may point to the absent man who wrote the letter.", + "references": "[Wikipedia: The Love Letter (Vermeer)](https://en.wikipedia.org/wiki/The_Love_Letter_(Vermeer))" + }, + { + "id": "steen-household-warnings", + "title": "Which details in Jan Steen's *Beware of Luxury* warn about where this household is heading?", + "description": "Painted in 1663. A slate propped in the corner reads *In weelde siet toe*, roughly 'In good times, beware'. In Dutch, 'a Jan Steen household' still means a chaotic home.", + "images": [ + { + "src": "steen-beware-of-luxury.webp", + "alt": "A crowded, chaotic room: a young woman in yellow satin holds a glass of wine next to a grinning man, an older woman sleeps at the table, and children raid a cupboard and play with a pipe and a string of pearls. A dog eats from a dish on the table, a monkey sits by a clock on the wall, a basket hangs from the ceiling, and a pig noses at a sprig of roses in the corner among scattered cards.", + "caption": "Jan Steen, *Beware of Luxury*, 1663. Kunsthistorisches Museum, Vienna. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Jan_Steen_-_Beware_of_Luxury_%28%E2%80%9CIn_Weelde_Siet_Toe%E2%80%9D%29_-_Google_Art_Project.jpg).", + "width": 1600, + "height": 1151 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The basket hanging overhead, holding a crutch, a sword and a leper's clapper", + "isCorrect": true + }, + { + "text": "The monkey playing with the clock", + "isCorrect": true + }, + { + "text": "The pig nosing at a rose", + "isCorrect": true + }, + { + "text": "The dog on the table eating the pie", + "isCorrect": true + }, + { + "text": "The sleeping housewife, resting after a day of honest work", + "isCorrect": false + }, + { + "text": "The cupboard, well stocked and neatly kept, a sign that order will return", + "isCorrect": false + } + ], + "explanation": "Steen built the picture as a joke with a sermon inside. The punishment hangs overhead: a basket holding a crutch, a sword and a clapper, the rattle a leper used to warn people away. Poverty, violence and disease are waiting to fall. The monkey fiddling with the clock recalls the saying that time is forgotten in folly. The pig nosing a rose acts out 'roses before swine', something precious wasted. The dog helps itself to the pie while nobody watches.\n\nThe two wrong answers describe the opposite of what you see. The lady of the house has dozed off, probably from drink, and the household is running wild. Her children are raiding the cupboard for glassware, and one of them is smoking a pipe. The man in front, modeled by Steen himself, laughs off a woman scolding him while cards and food litter the floor.", + "references": "[Wikipedia: Beware of Luxury](https://en.wikipedia.org/wiki/Beware_of_Luxury)" + }, + { + "id": "hogarth-tete-a-tete", + "title": "Hogarth's *The Tête à Tête* shows a young aristocratic couple shortly after their wedding. Which details show the marriage is already failing?", + "images": [ + { + "src": "hogarth-tete-a-tete.webp", + "alt": "An elegant, untidy drawing room: at right a young man in a hat slumps in a chair with his legs stretched out and a small dog beside him, at center a young woman stretches her arms beside a tea table, and at left an older man in black walks away holding papers with one hand raised. An overturned chair and an open music book lie on the carpet.", + "caption": "William Hogarth, *Marriage A-la-Mode: 2, The Tête à Tête*, about 1743. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Marriage_A-la-Mode_2,_The_T%C3%AAte_%C3%A0_T%C3%AAte_-_William_Hogarth.jpg).", + "width": 1600, + "height": 1219 + }, + { + "src": "hogarth-dog-and-sword-detail.webp", + "alt": "Detail of the slumped husband: a small grey dog sniffs at his coat pocket, and a sword lies on the carpet near his feet.", + "caption": "William Hogarth, *Marriage A-la-Mode: 2, The Tête à Tête*, about 1743 (detail). National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Marriage_A-la-Mode_2,_The_T%C3%AAte_%C3%A0_T%C3%AAte_-_William_Hogarth.jpg).", + "width": 1000, + "height": 967 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "A small dog is pulling a woman's lace cap out of the husband's pocket", + "isCorrect": true + }, + { + "text": "A broken sword lies at his feet", + "isCorrect": true + }, + { + "text": "The steward is walking away with a stack of unpaid bills", + "isCorrect": true + }, + { + "text": "The husband has just come home from a long day managing the family estate", + "isCorrect": false + }, + { + "text": "The couple is sharing a quiet breakfast, attentive to each other", + "isCorrect": false + } + ], + "explanation": "This is the second scene of *Marriage A-la-Mode*, Hogarth's six-picture story of an arranged marriage, painted around 1743 and sold to a wide public as engravings. It is the morning after a long night for both of them.\n\nThe husband slumps in his chair, still in his hat, and his dog sniffs at a lace cap poking out of his pocket. It isn't his wife's. The broken sword by his feet suggests a brawl. The wife stretches after her own night: the overturned chair and the music on the floor point to a party at home. At the left, the steward raises a hand in despair and walks off with a fistful of bills.\n\nThe couple aren't looking at each other at all. Hogarth meant his series to be read like a novel, one telling detail at a time.", + "references": "[National Gallery: Marriage A-la-Mode: 2, The Tête à Tête](https://www.nationalgallery.org.uk/paintings/william-hogarth-marriage-a-la-mode-2-the-tete-a-tete) · [Wikipedia: Marriage A-la-Mode (Hogarth)](https://en.wikipedia.org/wiki/Marriage_A-la-Mode_(Hogarth))" + }, + { + "id": "andrews-unfinished-lap", + "title": "There is a bare, unfinished patch on Mrs Andrews's lap. What was supposed to go there?", + "images": [ + { + "src": "gainsborough-mr-and-mrs-andrews.webp", + "alt": "A young man with a gun under his arm and a dog at his feet stands beside a young woman in a pale blue dress seated on a bench under an oak. To the right stretches a wide view of fields, wheatsheaves, pastures and a distant church under a cloudy sky.", + "caption": "Thomas Gainsborough, *Mr and Mrs Andrews*, about 1750. National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Thomas_Gainsborough_-_Mr_and_Mrs_Andrews.jpg).", + "width": 1600, + "height": 927 + }, + { + "src": "gainsborough-lap-detail.webp", + "alt": "Close-up of the woman's lap: her hands rest on a patch of flat brownish paint with no clear object in it, surrounded by the finished folds of her blue silk skirt.", + "caption": "Thomas Gainsborough, *Mr and Mrs Andrews*, about 1750 (detail). National Gallery, London. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Thomas_Gainsborough_-_Mr_and_Mrs_Andrews.jpg).", + "width": 845, + "height": 653 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Nobody knows for sure; a baby, a book, a fan or a bird shot by her husband have all been suggested", + "isCorrect": true + }, + { + "text": "A pet cat, painted out after the animal died", + "isCorrect": false + }, + { + "text": "Nothing: the patch is damage from a twentieth-century cleaning", + "isCorrect": false + }, + { + "text": "A basket of eggs, the traditional attribute of a new bride", + "isCorrect": false + } + ], + "explanation": "The canvas simply isn't finished there. Gainsborough left the area in her lap unpainted, and nobody knows why. The National Gallery suggests it may have been kept for a baby to be added later. Other guesses include a book, a fan, or a game bird shot by her husband, who stands with his gun and dog.\n\nThe rest of the picture is easier to read. It has been called a triple portrait, of Robert Andrews, his wife and his land. Most of the fields in view belonged to him. The neat wheatsheaves and well-stocked pastures show off a well-run estate, displayed as proudly as the couple themselves.", + "references": "[National Gallery: Mr and Mrs Andrews](https://www.nationalgallery.org.uk/paintings/thomas-gainsborough-mr-and-mrs-andrews) · [Wikipedia: Mr and Mrs Andrews](https://en.wikipedia.org/wiki/Mr_and_Mrs_Andrews)" + }, + { + "id": "night-watch-facts", + "title": "Which of these statements about Rembrandt's *Night Watch* are true?", + "images": [ + { + "src": "rembrandt-night-watch.webp", + "alt": "A huge group portrait: armed militiamen with muskets, pikes and a banner surge forward from a dark archway. At the center a captain in black with a red sash strides beside a lieutenant in pale yellow, and a small girl in a golden dress glows among the men at left.", + "caption": "Rembrandt, *The Night Watch*, 1642. Amsterdam Museum, on loan to the Rijksmuseum. Public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:The_Nightwatch_by_Rembrandt_-_Rijksmuseum.jpg).", + "width": 1600, + "height": 1302 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "It shows a daytime scene: darkened varnish made it look like night, and the nickname stuck", + "isCorrect": true + }, + { + "text": "It was cut down on all four sides in 1715 to fit a wall in Amsterdam's town hall", + "isCorrect": true + }, + { + "text": "The militiamen shown in it paid for it themselves", + "isCorrect": true + }, + { + "text": "The glowing girl with a dead chicken at her belt is a walking emblem of the company", + "isCorrect": true + }, + { + "text": "Rembrandt painted it by torchlight to record a real night patrol", + "isCorrect": false + }, + { + "text": "The company rejected it and never hung it", + "isCorrect": false + } + ], + "explanation": "Its real title is *Militia Company of District II under the Command of Captain Frans Banninck Cocq*, and the company is setting out in daylight. For most of its history the canvas was coated in varnish that darkened with age, which is how it came to be called the Night Watch. The varnish came off in the 1940s, but the name stayed.\n\nThe captain and members of his company paid Rembrandt about 1,600 guilders to be included. The painting hung in their hall until 1715. It was then moved to the town hall and trimmed on all four sides to fit between columns, losing figures at the left.\n\nThe small girl lit up in gold carries the company's symbol. The claws of the dead chicken at her belt refer to the *clauweniers*, the arquebusiers.", + "references": "[Rijksmuseum: The Night Watch](https://www.rijksmuseum.nl/en/collection/SK-C-5) · [Wikipedia: The Night Watch](https://en.wikipedia.org/wiki/The_Night_Watch)" + } + ] +} diff --git a/content/quizzes/reading-old-paintings/ambassadors-floor-smear.webp b/content/quizzes/reading-old-paintings/ambassadors-floor-smear.webp new file mode 100644 index 0000000..ae0ee6b Binary files /dev/null and b/content/quizzes/reading-old-paintings/ambassadors-floor-smear.webp differ diff --git a/content/quizzes/reading-old-paintings/ambassadors-full.webp b/content/quizzes/reading-old-paintings/ambassadors-full.webp new file mode 100644 index 0000000..106f378 Binary files /dev/null and b/content/quizzes/reading-old-paintings/ambassadors-full.webp differ diff --git a/content/quizzes/reading-old-paintings/ambassadors-skull-corrected.webp b/content/quizzes/reading-old-paintings/ambassadors-skull-corrected.webp new file mode 100644 index 0000000..1f83f6c Binary files /dev/null and b/content/quizzes/reading-old-paintings/ambassadors-skull-corrected.webp differ diff --git a/content/quizzes/reading-old-paintings/antonello-saint-jerome.webp b/content/quizzes/reading-old-paintings/antonello-saint-jerome.webp new file mode 100644 index 0000000..5b33e2b Binary files /dev/null and b/content/quizzes/reading-old-paintings/antonello-saint-jerome.webp differ diff --git a/content/quizzes/reading-old-paintings/arnolfini-bride-detail.webp b/content/quizzes/reading-old-paintings/arnolfini-bride-detail.webp new file mode 100644 index 0000000..c812bf9 Binary files /dev/null and b/content/quizzes/reading-old-paintings/arnolfini-bride-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/arnolfini-mirror-detail.webp b/content/quizzes/reading-old-paintings/arnolfini-mirror-detail.webp new file mode 100644 index 0000000..9a5009b Binary files /dev/null and b/content/quizzes/reading-old-paintings/arnolfini-mirror-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/arnolfini-portrait.webp b/content/quizzes/reading-old-paintings/arnolfini-portrait.webp new file mode 100644 index 0000000..e341967 Binary files /dev/null and b/content/quizzes/reading-old-paintings/arnolfini-portrait.webp differ diff --git a/content/quizzes/reading-old-paintings/berlinghiero-madonna.webp b/content/quizzes/reading-old-paintings/berlinghiero-madonna.webp new file mode 100644 index 0000000..df92206 Binary files /dev/null and b/content/quizzes/reading-old-paintings/berlinghiero-madonna.webp differ diff --git a/content/quizzes/reading-old-paintings/bosschaert-bouquet.webp b/content/quizzes/reading-old-paintings/bosschaert-bouquet.webp new file mode 100644 index 0000000..aa167d9 Binary files /dev/null and b/content/quizzes/reading-old-paintings/bosschaert-bouquet.webp differ diff --git a/content/quizzes/reading-old-paintings/chardin-soap-bubbles.webp b/content/quizzes/reading-old-paintings/chardin-soap-bubbles.webp new file mode 100644 index 0000000..00cdfdc Binary files /dev/null and b/content/quizzes/reading-old-paintings/chardin-soap-bubbles.webp differ diff --git a/content/quizzes/reading-old-paintings/crivelli-annunciation.webp b/content/quizzes/reading-old-paintings/crivelli-annunciation.webp new file mode 100644 index 0000000..ff415b3 Binary files /dev/null and b/content/quizzes/reading-old-paintings/crivelli-annunciation.webp differ diff --git a/content/quizzes/reading-old-paintings/crivelli-apple-cucumber-detail.webp b/content/quizzes/reading-old-paintings/crivelli-apple-cucumber-detail.webp new file mode 100644 index 0000000..9b8f331 Binary files /dev/null and b/content/quizzes/reading-old-paintings/crivelli-apple-cucumber-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/crivelli-emidius-detail.webp b/content/quizzes/reading-old-paintings/crivelli-emidius-detail.webp new file mode 100644 index 0000000..220bcbd Binary files /dev/null and b/content/quizzes/reading-old-paintings/crivelli-emidius-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/del-cossa-lucy-hand-detail.webp b/content/quizzes/reading-old-paintings/del-cossa-lucy-hand-detail.webp new file mode 100644 index 0000000..5cbbc45 Binary files /dev/null and b/content/quizzes/reading-old-paintings/del-cossa-lucy-hand-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/del-cossa-saint-lucy.webp b/content/quizzes/reading-old-paintings/del-cossa-saint-lucy.webp new file mode 100644 index 0000000..a5366be Binary files /dev/null and b/content/quizzes/reading-old-paintings/del-cossa-saint-lucy.webp differ diff --git a/content/quizzes/reading-old-paintings/gainsborough-lap-detail.webp b/content/quizzes/reading-old-paintings/gainsborough-lap-detail.webp new file mode 100644 index 0000000..dd78347 Binary files /dev/null and b/content/quizzes/reading-old-paintings/gainsborough-lap-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/gainsborough-mr-and-mrs-andrews.webp b/content/quizzes/reading-old-paintings/gainsborough-mr-and-mrs-andrews.webp new file mode 100644 index 0000000..6618a50 Binary files /dev/null and b/content/quizzes/reading-old-paintings/gainsborough-mr-and-mrs-andrews.webp differ diff --git a/content/quizzes/reading-old-paintings/hogarth-dog-and-sword-detail.webp b/content/quizzes/reading-old-paintings/hogarth-dog-and-sword-detail.webp new file mode 100644 index 0000000..d43b962 Binary files /dev/null and b/content/quizzes/reading-old-paintings/hogarth-dog-and-sword-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/hogarth-tete-a-tete.webp b/content/quizzes/reading-old-paintings/hogarth-tete-a-tete.webp new file mode 100644 index 0000000..839e691 Binary files /dev/null and b/content/quizzes/reading-old-paintings/hogarth-tete-a-tete.webp differ diff --git a/content/quizzes/reading-old-paintings/icarus-landscape.webp b/content/quizzes/reading-old-paintings/icarus-landscape.webp new file mode 100644 index 0000000..c51a2d1 Binary files /dev/null and b/content/quizzes/reading-old-paintings/icarus-landscape.webp differ diff --git a/content/quizzes/reading-old-paintings/icarus-legs-detail.webp b/content/quizzes/reading-old-paintings/icarus-legs-detail.webp new file mode 100644 index 0000000..8c6acaf Binary files /dev/null and b/content/quizzes/reading-old-paintings/icarus-legs-detail.webp differ diff --git a/content/quizzes/reading-old-paintings/last-supper-vanishing-point.webp b/content/quizzes/reading-old-paintings/last-supper-vanishing-point.webp new file mode 100644 index 0000000..50be47a Binary files /dev/null and b/content/quizzes/reading-old-paintings/last-supper-vanishing-point.webp differ diff --git a/content/quizzes/reading-old-paintings/last-supper.webp b/content/quizzes/reading-old-paintings/last-supper.webp new file mode 100644 index 0000000..d89e048 Binary files /dev/null and b/content/quizzes/reading-old-paintings/last-supper.webp differ diff --git a/content/quizzes/reading-old-paintings/manet-olympia.webp b/content/quizzes/reading-old-paintings/manet-olympia.webp new file mode 100644 index 0000000..c71eb67 Binary files /dev/null and b/content/quizzes/reading-old-paintings/manet-olympia.webp differ diff --git a/content/quizzes/reading-old-paintings/masaccio-trinity.webp b/content/quizzes/reading-old-paintings/masaccio-trinity.webp new file mode 100644 index 0000000..74eff23 Binary files /dev/null and b/content/quizzes/reading-old-paintings/masaccio-trinity.webp differ diff --git a/content/quizzes/reading-old-paintings/piero-misericordia.webp b/content/quizzes/reading-old-paintings/piero-misericordia.webp new file mode 100644 index 0000000..5131566 Binary files /dev/null and b/content/quizzes/reading-old-paintings/piero-misericordia.webp differ diff --git a/content/quizzes/reading-old-paintings/raphael-saint-catherine.webp b/content/quizzes/reading-old-paintings/raphael-saint-catherine.webp new file mode 100644 index 0000000..8c4b22f Binary files /dev/null and b/content/quizzes/reading-old-paintings/raphael-saint-catherine.webp differ diff --git a/content/quizzes/reading-old-paintings/rembrandt-night-watch.webp b/content/quizzes/reading-old-paintings/rembrandt-night-watch.webp new file mode 100644 index 0000000..000863b Binary files /dev/null and b/content/quizzes/reading-old-paintings/rembrandt-night-watch.webp differ diff --git a/content/quizzes/reading-old-paintings/sassoferrato-virgin-in-prayer.webp b/content/quizzes/reading-old-paintings/sassoferrato-virgin-in-prayer.webp new file mode 100644 index 0000000..83797ad Binary files /dev/null and b/content/quizzes/reading-old-paintings/sassoferrato-virgin-in-prayer.webp differ diff --git a/content/quizzes/reading-old-paintings/simone-martini-annunciation.webp b/content/quizzes/reading-old-paintings/simone-martini-annunciation.webp new file mode 100644 index 0000000..35cdf14 Binary files /dev/null and b/content/quizzes/reading-old-paintings/simone-martini-annunciation.webp differ diff --git a/content/quizzes/reading-old-paintings/steen-beware-of-luxury.webp b/content/quizzes/reading-old-paintings/steen-beware-of-luxury.webp new file mode 100644 index 0000000..0ca9ba2 Binary files /dev/null and b/content/quizzes/reading-old-paintings/steen-beware-of-luxury.webp differ diff --git a/content/quizzes/reading-old-paintings/steenwyck-vanitas.webp b/content/quizzes/reading-old-paintings/steenwyck-vanitas.webp new file mode 100644 index 0000000..5db6141 Binary files /dev/null and b/content/quizzes/reading-old-paintings/steenwyck-vanitas.webp differ diff --git a/content/quizzes/reading-old-paintings/titian-venus-of-urbino.webp b/content/quizzes/reading-old-paintings/titian-venus-of-urbino.webp new file mode 100644 index 0000000..5d90162 Binary files /dev/null and b/content/quizzes/reading-old-paintings/titian-venus-of-urbino.webp differ diff --git a/content/quizzes/reading-old-paintings/vermeer-love-letter.webp b/content/quizzes/reading-old-paintings/vermeer-love-letter.webp new file mode 100644 index 0000000..32f259f Binary files /dev/null and b/content/quizzes/reading-old-paintings/vermeer-love-letter.webp differ diff --git a/content/quizzes/reading-the-landscape.json b/content/quizzes/reading-the-landscape.json new file mode 100644 index 0000000..c6c48b1 --- /dev/null +++ b/content/quizzes/reading-the-landscape.json @@ -0,0 +1,987 @@ +{ + "schemaVersion": 1, + "id": "reading-the-landscape", + "title": "Reading the landscape: what the ground is telling you", + "description": "Landforms keep a record of what made them: a U-shaped valley, a line of springs along a cliff, rock layers meeting at an angle. Each question shows a photo of real terrain and asks what happened there, and the explanation draws the cross-section. The route runs from glaciated mountains through rivers, limestone and desert to the coast, and ends at the Scottish outcrop where James Hutton saw deep time.", + "language": "en", + "tags": [ + "geology", + "science", + "everyday-life" + ], + "author": "Quizbun", + "questions": [ + { + "id": "u-shaped-valley", + "title": "This valley has a broad, flat floor and steep walls that curve up to meet it. What carved it into this shape?", + "images": [ + { + "src": "glacial-valley-glengesh.webp", + "alt": "A wide valley between two steep, grassy mountainsides. Its floor is flat and farmed, and the walls curve smoothly down into it, giving the valley a rounded cross-section.", + "caption": "Photo by Wilson44691, CC0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Glacial_Valley_Ardara_Glengesh_Pass.jpg).", + "width": 1200, + "height": 900 + }, + { + "src": "u-versus-v-valley.svg", + "alt": "Two cross-sections side by side. A river valley forms a sharp V with a small stream at the bottom point. A glaciated valley forms a rounded U filled almost to the brim with ice, with a dashed trimline above the ice surface.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A glacier that filled the valley and eroded its floor and walls together", + "isCorrect": true + }, + { + "text": "A river that meandered back and forth for millions of years, widening the valley floor", + "isCorrect": false + }, + { + "text": "A fault that dropped the valley floor down between two blocks of crust", + "isCorrect": false + }, + { + "text": "Wind abrasion that sandblasted the lower slopes more than the upper ones", + "isCorrect": false + } + ], + "explanation": "A river erodes only where its water flows, in a narrow strip along the bed. As it cuts down, the slopes on either side slump and wash in, leaving a **V**. A valley glacier is hundreds of metres thick and fills the valley to a high level. Rock frozen into its base scours the floor, and the same ice presses against the walls, so it widens and deepens the whole cross-section at once. The result is a **U**: steep sides, a flat floor, and often a trimline high on the walls where the ice surface used to be.\n\nA meandering river can build a flat floor, but its valley sides stay gentle instead of curving steeply up. Valleys dropped between faults (grabens) usually have straight edges.", + "references": "[Wikipedia: U-shaped valley](https://en.wikipedia.org/wiki/U-shaped_valley) · [Wikipedia: Glacial landform](https://en.wikipedia.org/wiki/Glacial_landform)" + }, + { + "id": "hanging-valley-waterfall", + "title": "This waterfall pours off the lip of a side valley that ends high above the main valley floor. Why does the side valley stop so abruptly?", + "images": [ + { + "src": "bridalveil-fall.webp", + "alt": "A tall, narrow waterfall drops from a notch high in a sheer granite cliff into a forested valley. The creek above it comes out of a smaller valley whose floor sits far above the main one.", + "caption": "Photo by Brocken Inaglory, CC BY-SA 3.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:A_rainbow_over_Bridalveil_Fall_seen_from_Tunnel_View_in_Yosemite_NP.jpg).", + "width": 1200, + "height": 763 + }, + { + "src": "hanging-valley-step.svg", + "alt": "Cross-section of a small tributary glacier meeting a much larger trunk glacier. Both ice surfaces meet at the same height, but the trunk glacier's floor is far deeper. After the ice melts, the tributary stream falls off the resulting step as a waterfall.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A large trunk glacier deepened the main valley far more than the small glacier in the side valley could deepen its own floor", + "isCorrect": true + }, + { + "text": "The side valley's stream is younger than the main river, so it has not yet had time to cut down", + "isCorrect": false + }, + { + "text": "An earthquake lifted the side valley along a fault, leaving it stranded above the main floor", + "isCorrect": false + }, + { + "text": "Harder rock in the side valley resisted a river that cut easily through soft rock in the main valley", + "isCorrect": false + } + ], + "explanation": "This is Bridalveil Fall in Yosemite, which drops about 188 m from a **hanging valley**. During glaciations a thick trunk glacier filled Yosemite Valley, and smaller glaciers fed into it from the side valleys. Their ice surfaces joined at roughly the same level, but a thicker, faster glacier erodes harder, so the trunk glacier ground its floor down much further than the small tributary could.\n\nWhile the ice was there, you couldn't see the difference. Once it melted, the side valley was left perched on a cliff, and its stream now falls off the step. Rivers alone rarely do this. Where a tributary joins a river, the two floors usually meet at the same level, because the smaller stream cuts down to match the one it flows into.", + "references": "[Wikipedia: Hanging valley](https://en.wikipedia.org/wiki/Hanging_valley) · [Wikipedia: Bridalveil Fall](https://en.wikipedia.org/wiki/Bridalveil_Fall)" + }, + { + "id": "cirque-tarn-arete", + "title": "From the summit of Helvellyn you look down on a round lake in a steep-walled bowl, beside a knife-edged ridge. What can you read from this view?", + "images": [ + { + "src": "red-tarn-striding-edge.webp", + "alt": "A small, nearly round dark lake lying in a deep bowl with steep grassy and rocky back walls. To the right, a narrow, jagged ridge runs away from the viewer along the rim of the bowl.", + "caption": "Photo by Tim Stevens, CC BY 2.5, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Striding_Edge_and_Red_Tarn,_Helvellyn.jpg).", + "width": 752, + "height": 500 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The bowl is a cirque, cut where a small glacier started at the head of a valley", + "isCorrect": true + }, + { + "text": "The lake sits in a hollow that the ice dug below the level of the bowl's rim", + "isCorrect": true + }, + { + "text": "The sharp ridge was left standing as ice ate into it from both sides", + "isCorrect": true + }, + { + "text": "The bowl is the crater of an extinct volcano", + "isCorrect": false + }, + { + "text": "The ridge is a pile of rubble that a glacier dumped at its edge", + "isCorrect": false + } + ], + "explanation": "Snow collects in a sheltered hollow and turns to ice, and the ice starts to rotate slowly downhill. Along the back wall it plucks rock away, so the wall grows steeper. Beneath it, the rotating ice scrapes the floor down into a basin. The result is an armchair-shaped **cirque**, and the rock lip at its mouth often holds water once the ice melts. That lake is a **tarn**, in this case Red Tarn.\n\nWhen two cirques cut back toward each other, the rock between them narrows to a knife-edge called an **arête**. Striding Edge is one of the best-known in Britain. Helvellyn's rocks are volcanic, formed about 450 million years ago, which makes the crater option tempting. But this bowl was cut by ice long after any volcano had gone. A dumped rubble ridge would be a moraine, which is soft and rounded. An arête is bare bedrock, sharpened from both sides.", + "references": "[Wikipedia: Cirque](https://en.wikipedia.org/wiki/Cirque) · [Wikipedia: Arête](https://en.wikipedia.org/wiki/Ar%C3%AAte) · [Wikipedia: Helvellyn](https://en.wikipedia.org/wiki/Helvellyn)" + }, + { + "id": "roche-moutonnee-direction", + "title": "This bare rock hump has one long, smooth, gently rising side and one short, steep side broken into blocks. Glaciers made it. Which way was the ice moving?", + "images": [ + { + "src": "cadair-idris-rock.webp", + "alt": "A pale grey bedrock hump in a grassy mountain valley. Its left side rises in a long, smooth, rounded slope; its right end is shorter, steeper, and broken into angular, jagged blocks.", + "caption": "Photo by Jynto, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Roche_Moutonn%C3%A9e_Cadair_Idris.jpg).", + "width": 1200, + "height": 900 + }, + { + "src": "roche-moutonnee-stoss-lee.svg", + "alt": "Cross-section of a rock hump under a dashed ice surface, with ice flowing left to right. The gentle left slope is labelled stoss side, smoothed and scratched. The steep right end is labelled lee side, with blocks plucked away.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Up the smooth side and over the top, toward the steep, broken side", + "isCorrect": true + }, + { + "text": "Up the steep, broken side and over the top, toward the smooth side", + "isCorrect": false + }, + { + "text": "Along the length of the hump, parallel to both sides", + "isCorrect": false + }, + { + "text": "You cannot tell; the ice shapes both sides the same way", + "isCorrect": false + } + ], + "explanation": "This is a **roche moutonnée**. Where ice pushes against a rock obstacle, stones frozen into the glacier base grind the up-ice (**stoss**) side smooth and scratch it with striations. Over the top, pressure drops. Meltwater seeps into cracks on the down-ice (**lee**) side and refreezes onto the moving ice, which then pulls whole blocks away. That process is called plucking, and it leaves a steep, jagged face.\n\nSo the polished slope faces the direction the ice came from, and the broken face points where it went. Hold on to this one: a drumlin, a few Questions on, has the opposite profile.", + "references": "[Wikipedia: Roche moutonnée](https://en.wikipedia.org/wiki/Roche_moutonn%C3%A9e) · [Wikipedia: Plucking (glaciation)](https://en.wikipedia.org/wiki/Plucking_(glaciation))" + }, + { + "id": "drumlin-direction", + "title": "Low, smooth hills like this one come in swarms, all stretched the same way. Each has a steep, blunt end and a long, tapering tail. What shaped them, and which way does the blunt end face?", + "images": [ + { + "src": "clew-bay-island.webp", + "alt": "A long, low, grass-topped island rising from a flat sandy shore, with rippled sand in the foreground. The island is smoothly elongated, with a steeper, higher end on one side and a gentler slope on the other.", + "caption": "Photo by Brendanconway, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Drowned_drumlin_in_Clew_Bay.jpg).", + "width": 1200, + "height": 900 + }, + { + "src": "drumlin-stoss-lee.svg", + "alt": "Side view of a drumlin made of till, with ice flowing left to right. The steep, blunt end is on the left, facing up-ice, and a long tapering tail runs to the right, down-ice.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Moving ice molded them from sediment, and the blunt end faces the direction the ice came from", + "isCorrect": true + }, + { + "text": "Moving ice molded them from sediment, and the blunt end faces the direction the ice went", + "isCorrect": false + }, + { + "text": "Wind piled them up as dunes, and the blunt end faces downwind", + "isCorrect": false + }, + { + "text": "Rivers cut them out of an old plain, and the blunt end faces upstream", + "isCorrect": false + } + ], + "explanation": "These are **drumlins**. This one, in Clew Bay in Ireland, was partly drowned by the rising sea. Drumlins are made of **till**: the unsorted clay, sand and stones a glacier drags along its bed. Ice flowing over that sediment smears it into streamlined hills whose long axes line up with the flow, which is why they come in swarms that all point the same way.\n\nThe steep end usually faces **up-ice**, and the tail stretches out down-ice. That is the reverse of a roche moutonnée, where the gentle side faces the ice. A simple way to remember it: a drumlin is built of soft sediment, so it gets streamlined like a teardrop, blunt end first. A roche moutonnée is solid rock, so the ice rounds its upstream face and quarries its downstream one. A dune also has one steep side, but it faces downwind, and dunes aren't made of mixed clay and boulders.", + "references": "[Wikipedia: Drumlin](https://en.wikipedia.org/wiki/Drumlin) · [Wikipedia: Clew Bay](https://en.wikipedia.org/wiki/Clew_Bay)" + }, + { + "id": "esker-origin", + "title": "A road runs for kilometres along the crest of this narrow, winding ridge, with lakes on both sides. Its core is layers of sand and gravel. What built it?", + "images": [ + { + "src": "punkaharju-ridge.webp", + "alt": "Aerial view of a long, narrow, pine-covered ridge snaking across a large lake, with water close on both sides and a road running along its crest into the distance.", + "caption": "Photo by Ximonic (Simo Räsänen), CC BY-SA 4.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Punkaharju_esker_towards_Pentinniemi_in_Savonlinna,_Southern_Savonia,_Finland,_2022_June.jpg).", + "width": 1200, + "height": 884 + }, + { + "src": "esker-tunnel-cast.svg", + "alt": "Two panels. Left: a meltwater tunnel at the base of a glacier, partly filled with layered sand and gravel. Right: after the ice melts, the same sand and gravel stands as a rounded ridge called an esker.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A meltwater river flowing in a tunnel under the ice, which filled its own channel with sediment", + "isCorrect": true + }, + { + "text": "The front edge of a glacier, which bulldozed loose rock into a ridge as it advanced", + "isCorrect": false + }, + { + "text": "Lake waves that built a long sandbar out from the shore", + "isCorrect": false + }, + { + "text": "A lava flow that later weathered down into sand and gravel", + "isCorrect": false + } + ], + "explanation": "This is the Punkaharju ridge in Finland, an **esker**. Under a melting ice sheet, meltwater collects into rivers that flow through tunnels in and beneath the ice. Walls of ice hold the tunnel in place, so the river piles sand and gravel on its bed until the channel is choked. When the ice melts away, the sediment stays behind as a ridge in the shape of the old channel.\n\nTwo clues point to running water rather than ice. The sediment is **sorted and layered**, and eskers **wind** like the rivers that made them, sometimes for hundreds of kilometres. Because the water was under pressure, an esker can even climb gently uphill in places. Material bulldozed by an ice front would form a moraine, a jumble of every grain size across the valley. A sandbar would run along a shoreline, not wind across a lake. Eskers make well-drained, level routes, which is why so many carry roads and railways.", + "references": "[Wikipedia: Esker](https://en.wikipedia.org/wiki/Esker) · [Wikipedia: Punkaharju](https://en.wikipedia.org/wiki/Punkaharju)" + }, + { + "id": "terminal-moraine", + "title": "This hummocky ridge near New Zealand's Tasman Glacier is strewn with boulders of every size, from pebbles to blocks as big as sheep. What can you conclude about it?", + "images": [ + { + "src": "tasman-moraine-slope.webp", + "alt": "A grassy, hummocky slope under a blue sky, scattered with grey boulders of many different sizes, from small stones to large blocks, lying at random.", + "caption": "Photo by Krzysztof Golik, CC BY-SA 4.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Terminal_moraine_of_Tasman_Glacier.jpg).", + "width": 1200, + "height": 800 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "It was dumped by ice, which carries everything from clay to boulders and drops it all together", + "isCorrect": true + }, + { + "text": "It marks a line where a glacier's front stood still long enough to pile up debris", + "isCorrect": true + }, + { + "text": "Ridges like this often hold back a lake once the glacier retreats behind them", + "isCorrect": true + }, + { + "text": "Its material was sorted into layers by running water", + "isCorrect": false + }, + { + "text": "It formed under the glacier, streamlined by the ice flowing over it", + "isCorrect": false + } + ], + "explanation": "The giveaway is **no sorting**. Water and wind sort sediment, because at any speed they can move grains up to a certain size and no bigger. Ice can carry a boulder and a clay particle side by side. When it melts, it drops them together, as unsorted **till**.\n\nA glacier works like a conveyor belt. Even when its front holds still, ice keeps flowing forward and melting, and debris piles up at the margin as a **moraine** ridge. A terminal moraine marks the farthest point the ice reached. When the glacier retreats, the ridge often acts as a dam, and the Tasman Glacier's own meltwater lake sits behind moraines like this one.\n\nSorted, layered gravel would point to meltwater, as in an esker. A streamlined hill shaped beneath the ice would be a drumlin, and that shape would line up with the ice flow, not run across the valley.", + "references": "[Wikipedia: Moraine](https://en.wikipedia.org/wiki/Moraine) · [Wikipedia: Tasman Glacier](https://en.wikipedia.org/wiki/Tasman_Glacier)" + }, + { + "id": "glacial-erratic", + "title": "This lone granite boulder sits in a Connecticut wood. What would best show that ice carried it here, rather than it rolling downhill or weathering out on the spot?", + "images": [ + { + "src": "ledyard-boulder.webp", + "alt": "A large, grey, speckled granite boulder, taller than a person, resting on leaf litter among small trees, with a few much smaller stones around its base.", + "caption": "Photo by Marty Aligata, CC BY-SA 4.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Glacial_Erratic_in_Ledyard,_CT.jpg).", + "width": 1200, + "height": 900 + } + ], + "type": "single-choice", + "options": [ + { + "text": "It is a different rock type from the bedrock beneath it, and matching outcrops lie many kilometres away in the direction the ice came from", + "isCorrect": true + }, + { + "text": "It is far larger than any other stone nearby, too big for a stream to have moved", + "isCorrect": false + }, + { + "text": "Its corners are rounded off, as if it had been tumbled a long way", + "isCorrect": false + }, + { + "text": "Thick moss and lichen cover it, so it has sat there for thousands of years", + "isCorrect": false + } + ], + "explanation": "A **glacial erratic** is identified by what it is made of, not by its size or shape. Geologists compare the boulder with the bedrock underneath. If they don't match, it came from somewhere else. If a matching outcrop lies up-ice, and there is no slope or river that could have moved it, ice is the only carrier left. Erratics traced this way helped convince 19th-century geologists that ice sheets once covered northern Europe and North America.\n\nSize alone proves nothing. A local outcrop can weather into big blocks, and rockfalls deliver huge boulders downhill. Rounding happens in rivers and on beaches too. A coat of lichen only tells you the surface has been exposed for a while.", + "references": "[Wikipedia: Glacial erratic](https://en.wikipedia.org/wiki/Glacial_erratic)" + }, + { + "id": "scree-sorting", + "title": "Below these cliffs, loose rock has built up into long, straight-sided slopes of scree. Where on a scree slope do you usually find the biggest blocks?", + "images": [ + { + "src": "spiti-slope.webp", + "alt": "Jagged, grey rock cliffs rising above long, smooth, fan-shaped slopes of loose grey rubble that spread down to a flat, green valley floor with a few buildings.", + "caption": "Photo by Timothy A. Gonsalves, CC BY-SA 4.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Losar_Spiti_Scree_Left_Himachal_Jun18_D72_7825.jpg).", + "width": 1200, + "height": 800 + } + ], + "type": "single-choice", + "options": [ + { + "text": "At the foot, because big blocks carry more momentum and roll past the smaller stones", + "isCorrect": true + }, + { + "text": "At the top, because big blocks are too heavy to move far from the cliff", + "isCorrect": false + }, + { + "text": "In the middle, where the slope is steepest", + "isCorrect": false + }, + { + "text": "Scattered evenly, because rockfall is random", + "isCorrect": false + } + ], + "explanation": "This scree is in Spiti, in the Indian Himalaya. Water freezes in cracks in the cliff and expands by about 9%, and repeated freeze-thaw prises blocks loose until they fall. Each new piece bounces and rolls down the pile. Small fragments lose their momentum quickly and lodge in gaps near the top. Big blocks carry more momentum, roll over the small stuff, and end up at the bottom. So, oddly, the heaviest pieces travel furthest, and scree slopes are usually well sorted, with the coarsest blocks at the foot.\n\nThe straight profile comes from the **angle of repose**, the steepest angle at which loose rock stays put, usually somewhere in the 30s of degrees. Add more rubble on top and the excess slides until the slope drops back to that angle. Slopes that are no longer being fed usually relax to a gentler, slightly concave profile.", + "references": "[Wikipedia: Scree](https://en.wikipedia.org/wiki/Scree) · [Wikipedia: Angle of repose](https://en.wikipedia.org/wiki/Angle_of_repose)" + }, + { + "id": "spring-line-weeping-rock", + "title": "At Weeping Rock in Zion, water drips from one level across an overhanging cliff, and ferns grow in a band along it. What can you conclude from where the water comes out?", + "images": [ + { + "src": "weeping-rock.webp", + "alt": "A curved, overhanging sandstone alcove streaked with dark water stains. Water drips from a line near its base, where a band of green plants hangs along the rock above a railed walkway with visitors.", + "caption": "Photo by Elin Beckmann, CC BY 2.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Weeping_Rock,_Zion.jpg).", + "width": 1200, + "height": 900 + }, + { + "src": "spring-line-contact.svg", + "alt": "Cross-section of porous sandstone lying above a less permeable siltstone and mudstone layer. Arrows show water sinking down through the sandstone, then turning sideways along the top of the lower layer and seeping out at the cliff face, where an alcove forms.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "A rock that lets water through lies on top of one that lets water through far less easily", + "isCorrect": true + }, + { + "text": "The water fell as rain or snow on the plateau above and soaked down through the rock", + "isCorrect": true + }, + { + "text": "The seeps line up because they all come out at the same boundary between rock layers", + "isCorrect": true + }, + { + "text": "The upper rock must be watertight, so rain runs off its surface and down the cliff", + "isCorrect": false + }, + { + "text": "The water is being pushed up from the river at the canyon floor", + "isCorrect": false + } + ], + "explanation": "Water sinks through a permeable rock until it reaches a layer it can't easily enter. It then spreads sideways along the top of that layer and comes out wherever the boundary meets the surface. Springs therefore line up along a **spring line** that follows the contact between the two rocks.\n\nIn Zion, the upper rock is the Navajo Sandstone, cemented dune sand that the park service describes as \"porous like a sponge\". Below it lie the siltstones and mudstones of the Kayenta Formation, which pass water far less readily. Rain and snowmelt soak into the plateau and emerge at the contact. The wet rock crumbles, the alcove grows, and ferns and other plants form a hanging garden in the constant drip.\n\nThe same logic explains the spring-line villages along the foot of England's chalk downs. Chalk lies over clay, and the villages grew where the water comes out.", + "references": "[NPS: Weeping Rock](https://www.nps.gov/places/weeping-rock.htm) · [Wikipedia: Spring (hydrology)](https://en.wikipedia.org/wiki/Spring_(hydrology)) · [Wikipedia: Kayenta Formation](https://en.wikipedia.org/wiki/Kayenta_Formation)" + }, + { + "id": "karst-clues", + "title": "Malham Cove is an 80-metre curved limestone cliff with a dry lip. A stream flows out of a cave at its foot, and on the moor above, about a mile away, another stream disappears into the ground. What does this tell you about limestone country?", + "images": [ + { + "src": "malham-cove.webp", + "alt": "A huge, pale grey, curved limestone cliff rising above a grassy valley with a drystone wall. A darker vertical streak runs down the middle of the cliff face beneath a dry notch in its lip.", + "caption": "Photo by F. C. Franklin, CC BY-SA 3.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Malham_Cove_1.jpg).", + "width": 1200, + "height": 900 + }, + { + "src": "karst-underground-drainage.svg", + "alt": "Cross-section of layered limestone. A stream on the moor sinks into a crack and flows underground through dissolved channels, emerging at the foot of the cliff. The cliff's lip, marked as an old waterfall, is now dry.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "Rainwater, made slightly acidic by dissolved carbon dioxide, widens cracks in limestone into underground channels", + "isCorrect": true + }, + { + "text": "Rivers can disappear at the surface and come back out somewhere else entirely", + "isCorrect": true + }, + { + "text": "A valley can stay dry even though a river once flowed through it", + "isCorrect": true + }, + { + "text": "The cliff's dry lip means the local climate is too dry for rivers today", + "isCorrect": false + }, + { + "text": "The stream at the foot must be the same water that sank nearest the cliff", + "isCorrect": false + } + ], + "explanation": "Rain picks up carbon dioxide from the air and soil and becomes a weak carbonic acid. Limestone dissolves in it. Over thousands of years, water widens joints and bedding planes into fissures and caves. Surface streams find these openings and sink, a hallmark of **karst** landscapes.\n\nMalham Cove shows the whole story. At the end of the last Ice Age, when the ground was frozen and the cracks sealed, a huge waterfall of meltwater poured over this lip. Today the water goes underground before it reaches the edge, leaving the lip dry. The dark streak marks where the fall once ran. The climate is still wet; the water has simply found a route underground.\n\nThe last option is a trap. Dye tests showed that the stream sinking at Water Sinks, near Malham Tarn, does *not* feed the stream at the cove's foot. It comes out at Aire Head, south of the village. The two flows cross behind the cliff without mixing. Underground drainage in karst rarely follows the surface map.", + "references": "[Wikipedia: Malham Cove](https://en.wikipedia.org/wiki/Malham_Cove) · [Wikipedia: Karst](https://en.wikipedia.org/wiki/Karst)" + }, + { + "id": "cover-collapse-sinkhole", + "title": "This crater opened in an Alabama wood in 1972, apparently all at once. There is limestone underneath and a thick layer of clay on top. Why can a hole like this appear so suddenly?", + "images": [ + { + "src": "december-giant.webp", + "alt": "Black-and-white aerial photo of a large, steep-walled oval hole in a forest, with bare, collapsed walls and a pool of water at the bottom.", + "caption": "Photo by T. V. Stone, USGS, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:December_Giant_sinkhole_collapse_USGS_1972.jpg).", + "width": 1200, + "height": 940 + }, + { + "src": "cover-collapse-stages.svg", + "alt": "Three panels of clay over limestone. First, a small void opens where grains wash down into a limestone cavity. Second, the void grows upward as a thin clay arch while the surface still looks normal. Third, the roof fails and the surface drops into a steep-sided hole.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Sticky clay bridged a cavity that grew upward out of sight, until the thin roof could no longer hold", + "isCorrect": true + }, + { + "text": "The limestone bedrock dissolved in a single wet season", + "isCorrect": false + }, + { + "text": "An underground gas explosion blew out the crater", + "isCorrect": false + }, + { + "text": "A small meteorite impact that left no fragments", + "isCorrect": false + } + ], + "explanation": "This is a **cover-collapse sinkhole**, and it forms from below. Water moving down through cracks in the limestone washes clay and sand into the voids underneath. A cavity opens at the base of the clay. Clay is cohesive, so it arches over the hole the way a stone bridge spans a gap, and the cavity migrates upward as its roof keeps spalling. At the surface nothing seems to change, until the last thin layer gives way.\n\nThe limestone dissolved over thousands of years; only the final failure was fast. A USGS report on human-induced sinkholes opens with this photo and calls it the largest collapse in Alabama. That report explains a common trigger: pumping groundwater lowers the water table and takes away the buoyancy that was helping to hold the clay up. That is why sinkholes cluster around wells, quarries and dewatered mines.", + "references": "[USGS Circular 968: Development of sinkholes resulting from man's activities in the eastern United States](https://pubs.usgs.gov/circ/1987/0968/report.pdf) · [Wikipedia: Sinkhole](https://en.wikipedia.org/wiki/Sinkhole)" + }, + { + "id": "meander-banks", + "title": "Look at any loop of this river: one bank is a gentle bar of gravel and sand, and the other is a steep, eroding edge. Which statements about a river bend are true?", + "images": [ + { + "src": "skagit-bend.webp", + "alt": "A green river valley seen from a mountainside. A wide river swings through broad loops, with pale gravel bars on the inside of the bends and forested banks on the outside.", + "caption": "Photo by Walter Siegmund, CC BY-SA 3.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Skagit_River_8667s.JPG).", + "width": 1200, + "height": 745 + }, + { + "src": "meander-cross-section.svg", + "alt": "Cross-section of a river bend. The outer bank is a steep cut bank with the deepest, fastest water against it. The inner bank slopes gently up as a point bar of sand and gravel deposited in slow water.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "The steep, eroding bank is on the outside of the bend", + "isCorrect": true + }, + { + "text": "The deepest water runs close to the outer bank", + "isCorrect": true + }, + { + "text": "Sand and gravel settle on the inside of the bend, where the water slows", + "isCorrect": true + }, + { + "text": "Over time the bend moves toward its inner bank", + "isCorrect": false + }, + { + "text": "The water flows fastest along the inner bank because it has less distance to travel", + "isCorrect": false + } + ], + "explanation": "As water rounds a bend, its momentum carries the fastest flow toward the outside. There it scours the bed deepest and undercuts the bank, which collapses into a steep **cut bank**. On the inside, the water is shallow and slow. It can't hold its coarser sediment, so it drops it as a sloping **point bar**. A corkscrew-shaped (helical) current also sweeps sediment across the bed from the outer bank toward the inner one.\n\nErosion on the outside and deposition on the inside mean the whole bend migrates **outward** and slowly downstream. Loops keep growing until something cuts them off, which is the next Question. This view shows the Skagit River in Washington, with pale gravel bars on the inner side of its bends.", + "references": "[Wikipedia: Meander](https://en.wikipedia.org/wiki/Meander) · [Wikipedia: Point bar](https://en.wikipedia.org/wiki/Point_bar) · [Wikipedia: Cut bank](https://en.wikipedia.org/wiki/Cut_bank)" + }, + { + "id": "oxbow-lake", + "title": "From the air, this river is surrounded by crescent-shaped lakes whose curves match the river's own loops. How did they get cut off?", + "images": [ + { + "src": "yamal-river.webp", + "alt": "Aerial view of a flat, wet plain with a winding river whose pale sandbars trace tight loops. Around it lie many crescent- and horseshoe-shaped lakes and faint curved scars matching the river's bends.", + "caption": "Photo by katorisi, CC BY-SA 3.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Oxbow_lake,Yamal_Peninsula,Russia.JPG).", + "width": 1200, + "height": 803 + }, + { + "src": "oxbow-cutoff-stages.svg", + "alt": "Three stages of a river loop. First, the outer banks of a tight loop erode toward each other and the neck narrows. Second, a flood breaks through the neck and the water takes the short cut. Third, sediment seals both ends of the abandoned loop, leaving a crescent lake.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Loops grew until two bends nearly met, then the river broke through the narrow neck and sealed off the old loop", + "isCorrect": true + }, + { + "text": "Each lake is a separate kettle hole left by a buried block of glacier ice", + "isCorrect": false + }, + { + "text": "Beavers dammed the river at many points, backing water into its old bends", + "isCorrect": false + }, + { + "text": "The ground sank along curved faults beside the river", + "isCorrect": false + } + ], + "explanation": "Meander bends grow outward, as the previous Question showed. On a tight loop, the outer banks on either side of the **neck** erode toward each other. Eventually a flood breaks through, often during high water, and the river takes the short, steep route. The old loop now carries almost no flow. Sediment settles at both ends where it meets the river, sealing the loop into an **oxbow lake**.\n\nThat is why these lakes keep the exact curve and width of a river channel. Kettle lakes are roughly round and scattered at random. Here, on the Yamal Peninsula in Arctic Russia, you can see every stage: live loops, fresh cutoffs, and older crescents fading into faint scars as they fill with silt and plants.", + "references": "[Wikipedia: Oxbow lake](https://en.wikipedia.org/wiki/Oxbow_lake)" + }, + { + "id": "river-terraces-age", + "title": "This shaded-relief map shows a river valley in Japan (green is low, orange is high). The valley sides rise in a staircase of flat steps. Which step is the oldest?", + "images": [ + { + "src": "numata-relief.webp", + "alt": "A colour shaded-relief elevation map. Two green valleys join in a Y. On both sides, the land rises in several broad, flat, sharply edged steps, shaded from green at the valley floor through yellow to orange on the high ground.", + "caption": "Photo by Batholith, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:River_terraces,_Numata,_Gunma,_Japan.jpg).", + "width": 1200, + "height": 900 + }, + { + "src": "terrace-flight-ages.svg", + "alt": "Cross-section of a valley with three pairs of flat steps on each side above today's river. The highest step is labelled oldest, the next younger, and the lowest younger still, just above today's river channel.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The highest step, farthest above today's river", + "isCorrect": true + }, + { + "text": "The lowest step, closest to today's river", + "isCorrect": false + }, + { + "text": "They formed at the same time, as one flood deposit that later cracked into steps", + "isCorrect": false + }, + { + "text": "The river channel itself, because the river was there first", + "isCorrect": false + } + ], + "explanation": "Each flat step is a **river terrace**: a former floodplain. A river builds a floodplain at its own level. If the land rises, the sea level falls, or the climate changes the river's load, the river starts **cutting down**. The old floodplain is left high and dry as a terrace, and a new floodplain forms at the lower level. Repeat that, and you get a staircase.\n\nThe river can only abandon a surface above it, so the **highest terrace is the oldest**, and each lower step is younger. That reverses the usual rule of stacked sediment, where the bottom layer is the oldest. Terraces aren't stacked; they're cut into each other. The present channel is the youngest feature of all. This site is Numata in Gunma Prefecture, Japan, whose terrace staircase is especially well developed.", + "references": "[Wikipedia: Fluvial terrace](https://en.wikipedia.org/wiki/Fluvial_terrace)" + }, + { + "id": "alluvial-fan", + "title": "Seen from above, where each canyon opens onto Death Valley's flat floor, a cone of gravel spreads out. Why does the sediment pile up right at the canyon mouth?", + "images": [ + { + "src": "death-valley-canyon-mouth.webp", + "alt": "View from a high ridge down a steep, eroded desert canyon. Beyond, on the flat valley floor, a broad, pale, fan-shaped apron of sediment spreads out from the base of the mountains.", + "caption": "Photo by Wilson44691, CC0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Death_Valley_Alluvial_Fan_from_Dante%27s_View.JPG).", + "width": 1200, + "height": 803 + }, + { + "src": "alluvial-fan-sorting.svg", + "alt": "A narrow canyon opening onto a flat plain. Arrows show the flow spreading out, slowing, and soaking into gravel. Large boulders lie near the apex of the fan, grading to small sand and silt grains at its toe.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The confined flow spreads out, gets shallower and slower, and soaks into the ground, so it can no longer carry its load", + "isCorrect": true + }, + { + "text": "The wind blows fine sediment back toward the mountains, where it piles against their base", + "isCorrect": false + }, + { + "text": "The canyon mouth is the lowest point in the valley, so everything drains and collects there", + "isCorrect": false + }, + { + "text": "Rockfall from the canyon walls tumbles out and lands in a cone at the entrance", + "isCorrect": false + } + ], + "explanation": "In a canyon, a flash flood is squeezed into a narrow channel, so it runs deep and fast and can move boulders. At the mountain front the walls end. The water spreads sideways, gets shallow, slows, and sinks into dry gravel. Its carrying capacity collapses, so it drops its load right there. The biggest pieces fall first near the apex, and finer sand and silt travel further out to the toe.\n\nEach flood shifts to a new path across the pile, so over time the deposit grows into a symmetrical cone: an **alluvial fan**. The lowest point of Death Valley is out on the salt flats, far from the canyon mouths, so the fan sits where the flow changed, not where the ground is lowest. Rockfall builds scree cones directly under cliffs, not smooth fans reaching kilometres onto the valley floor.", + "references": "[Wikipedia: Alluvial fan](https://en.wikipedia.org/wiki/Alluvial_fan)" + }, + { + "id": "fault-scarp", + "title": "This fresh step in the ground appeared in a single morning in 1983, running for kilometres along the foot of Idaho's Lost River Range. What made it?", + "images": [ + { + "src": "borah-peak-1983.webp", + "alt": "Black-and-white photo of a sagebrush plain crossed by a sharp, raw step in the ground with a crumbling face, running in a straight line into the distance.", + "caption": "Photo by R. E. Wallace, USGS, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Fault_Scarp_Borah_Peak_Earthquake_1983.jpg).", + "width": 1200, + "height": 1156 + }, + { + "src": "normal-fault-scarp.svg", + "alt": "Cross-section of a normal fault. The crust is pulled apart and the hanging-wall block slides down along an inclined fault plane, leaving a fresh step, the scarp, where the fault meets the surface. The footwall stays higher.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "An earthquake, when the ground on one side of a fault dropped down relative to the other", + "isCorrect": true + }, + { + "text": "A landslide that slumped away from the mountain front after heavy rain", + "isCorrect": false + }, + { + "text": "Erosion along the bank of an old stream channel that has since dried up", + "isCorrect": false + }, + { + "text": "Soil that shrank and cracked open during a long drought", + "isCorrect": false + } + ], + "explanation": "This is a **fault scarp** from the magnitude 6.9 Borah Peak earthquake of 28 October 1983. The Basin and Range region is being stretched. When accumulated strain on the Lost River Fault released, the valley-side block slid down the inclined fault plane, relative to the mountain side. The ground broke along a zone about 34 km long, with a throw of up to about 2.7 m.\n\nTwo things mark a fault rather than a landslide or a stream. The step runs **straight across the terrain**, ignoring ridges and gullies, because it follows a plane cutting down into the crust. And the same fault has moved many times before: the whole mountain range is the sum of thousands of these steps.", + "references": "[Wikipedia: 1983 Borah Peak earthquake](https://en.wikipedia.org/wiki/1983_Borah_Peak_earthquake) · [Wikipedia: Fault scarp](https://en.wikipedia.org/wiki/Fault_scarp)" + }, + { + "id": "cross-bedding-wind", + "title": "Inside this sandstone, thin layers are stacked in sets, and within each set they tilt the same way before being cut off by the set above. What made these tilted layers?", + "images": [ + { + "src": "zion-sandstone.webp", + "alt": "Close view of pink and cream sandstone with small shrubs. Thin, closely spaced layers sweep across the rock at an angle in several stacked sets, each set truncated at its top by the next.", + "caption": "Photo by Zion National Park, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Sandstone_Cross-bedding_%285930953092%29.jpg).", + "width": 1200, + "height": 848 + }, + { + "src": "dune-cross-bedding.svg", + "alt": "A dune with wind blowing left to right. Grains hop up the gentle windward side and avalanche down the steep lee face, building inclined layers. Below, the same pattern is shown as rock: stacked sets of inclined layers, all dipping downwind, each set's top cut off by the next.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Migrating sand dunes, with each tilted layer marking a sand avalanche down the dune's downwind face", + "isCorrect": true + }, + { + "text": "Earth movements that tilted flat layers after they had hardened", + "isCorrect": false + }, + { + "text": "Ocean waves washing sand up and down a beach", + "isCorrect": false + }, + { + "text": "Mud cracks that filled with sand", + "isCorrect": false + } + ], + "explanation": "This is **cross-bedding** in Zion's Navajo Sandstone, a desert sea of dunes turned to stone about 190 million years ago. Wind rolls and bounces grains up the gentle windward side of a dune. When the crest gets too steep, sand avalanches down the lee face, laying down a thin layer inclined at up to about 30–34°. As the dune advances, it buries layer after layer of these slopes.\n\nWhen the next dune passes over, it planes off the top of the old one. That is why each set is truncated. The layers **dip downwind**, so geologists measure them to map ancient wind directions. The tilting happened as the sand was laid down, not later: a later tilt would rotate every layer, including the boundaries between sets, by the same amount.", + "references": "[Wikipedia: Cross-bedding](https://en.wikipedia.org/wiki/Cross-bedding) · [Wikipedia: Navajo Sandstone](https://en.wikipedia.org/wiki/Navajo_Sandstone)" + }, + { + "id": "butte-caprock", + "title": "Monument Valley's buttes stand alone on a flat desert, each with a flat top and vertical sides. Why does erosion leave them this shape?", + "images": [ + { + "src": "west-mitten.webp", + "alt": "A tall, flat-topped red rock tower with sheer vertical walls rising from a sloping skirt of rubble, standing alone on a flat red desert with scrubby bushes and a dead tree in front.", + "caption": "Photo by Tobi 87, CC BY-SA 4.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Monument_Valley_West_Mitten_Butte.jpg).", + "width": 1200, + "height": 568 + }, + { + "src": "caprock-mesa-butte.svg", + "alt": "Three stages side by side: a wide mesa, a narrower butte and a thin spire, each topped by a thin layer of hard caprock over softer red layers, standing on slopes of weak shale.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A tough layer on top protects the rock below, while weaker layers underneath wear back and undercut the cliffs, so the walls retreat without lowering the top", + "isCorrect": true + }, + { + "text": "They are the necks of ancient volcanoes, whose hardened lava resisted erosion better than the rock around it", + "isCorrect": false + }, + { + "text": "Sand-laden wind cut their sides vertical, but only up to the height the blown sand could reach", + "isCorrect": false + }, + { + "text": "They are ancient reefs that grew straight upward from a shallow sea floor", + "isCorrect": false + } + ], + "explanation": "The rock here is flat-lying layers of different strength. A hard **caprock** on top resists weathering from above, so the summit stays flat. The cliffs retreat sideways instead. Weaker beds lower down crumble into sloping aprons, undercutting the stronger rock above, which breaks off along vertical joints. The walls stay steep as they back away.\n\nOver time a broad **mesa** shrinks to a **butte**, then to a narrow spire, and finally to nothing. The flat tops of Monument Valley's buttes line up at roughly the same height because they are remnants of one continuous plateau. Volcanic necks, like Shiprock in New Mexico, are made of solidified magma and look very different: jagged, not layered.", + "references": "[Wikipedia: Butte](https://en.wikipedia.org/wiki/Butte) · [Wikipedia: Monument Valley](https://en.wikipedia.org/wiki/Monument_Valley)" + }, + { + "id": "cross-cutting-dikes", + "title": "In this canyon wall, pale pink bands slice straight across the dark, streaky rock. Which formed first?", + "images": [ + { + "src": "black-canyon-wall.webp", + "alt": "A steep, dark grey rock wall with faint streaky layering, crossed by several pale pink bands that run at angles across it, some branching and cutting through each other.", + "caption": "Photo by Daniel Mayer (Mav), CC BY-SA 3.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Black_Canyon_of_the_Gunnison_National_Park_in_September_2011_-_dikes_in_canyon_wall.JPG).", + "width": 1200, + "height": 1134 + }, + { + "src": "cross-cutting-dike.svg", + "alt": "Dark rock with wavy layering, cut by two orange bands of once-molten rock labelled younger. Captions: first the dark rock forms and is folded, then magma forces into cracks across it.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The dark rock; the pink bands formed later when molten rock forced its way into cracks through it", + "isCorrect": true + }, + { + "text": "The pink bands; the dark rock was deposited around them later, filling the spaces between", + "isCorrect": false + }, + { + "text": "Both at once, as a single cooling rock separated into light and dark minerals", + "isCorrect": false + }, + { + "text": "You can't tell the order without laboratory dating", + "isCorrect": false + } + ], + "explanation": "The **principle of cross-cutting relationships** says a feature that cuts across a rock must be younger than the rock it cuts. You can't slice through something that doesn't exist yet. The pink bands here are **dikes**, sheets of once-molten rock (mostly pegmatite) that squeezed into fractures in the older dark metamorphic rock of the Black Canyon of the Gunnison, Colorado, and crystallised there.\n\nThe same rule works on dikes that cross each other: the one that goes straight through is younger than the one it interrupts. Nicolas Steno and later James Hutton turned simple rules like this into a way of putting events in order long before anyone could put numbers on them. That is called relative dating, and it needs no laboratory.", + "references": "[Wikipedia: Cross-cutting relationships](https://en.wikipedia.org/wiki/Cross-cutting_relationships) · [Wikipedia: Black Canyon of the Gunnison National Park](https://en.wikipedia.org/wiki/Black_Canyon_of_the_Gunnison_National_Park)" + }, + { + "id": "columnar-basalt", + "title": "The Giant's Causeway is a pavement of tightly fitting stone columns, mostly six-sided. What made them?", + "images": [ + { + "src": "causeway-tops.webp", + "alt": "Close-up of the flat tops of dark stone columns fitting together like paving, most with five or six straight sides, with grass growing in the narrow joints between them.", + "caption": "Photo by Suzanne Mischyshyn, CC BY-SA 2.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:County_Antrim_-_Giant%27s_Causeway_-_Tops_of_Basalt_Columns_-_geograph.org.uk_-_3987884.jpg).", + "width": 1200, + "height": 801 + }, + { + "src": "columnar-jointing.svg", + "alt": "Two panels. Side view of a cooling lava flow with cracks growing inward from its top and base toward a dashed line in the middle. Top view of a honeycomb of hexagonal columns.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "A thick lava flow shrank as it cooled and cracked into a network of columns", + "isCorrect": true + }, + { + "text": "Each column is a single giant crystal that grew from the lava", + "isCorrect": false + }, + { + "text": "Waves eroded the rock along a regular pattern of weaknesses left by earthquakes", + "isCorrect": false + }, + { + "text": "Sediment filled the cells of an ancient coral reef and hardened", + "isCorrect": false + } + ], + "explanation": "About 50 to 60 million years ago, fluid basalt lava ponded here in thick flows. Rock contracts as it cools, so the flow surfaces shrank but were held in place and cracked, much as drying mud does. Once started, the cracks grew into the flow at right angles to the cooling surfaces, from the top down and the bottom up. They split the lava into long parallel **columns**.\n\nA crack network relieves stress most efficiently when cracks meet at about 120°, so hexagons dominate. They are not perfect: the columns here have between four and eight sides, which a crystal would not allow. The columns are fine-grained basalt made of many tiny crystals, not one giant one.", + "references": "[Wikipedia: Giant's Causeway](https://en.wikipedia.org/wiki/Giant%27s_Causeway) · [Wikipedia: Columnar jointing](https://en.wikipedia.org/wiki/Columnar_jointing)" + }, + { + "id": "sea-stack-sequence", + "title": "Off this chalk headland, a big block has a tunnel through its base, and a lone pillar stands further out. What was the lone pillar before it stood alone?", + "images": [ + { + "src": "old-harry.webp", + "alt": "Brilliant white chalk rising from blue sea: a large, grass-topped block with a dark cave and arch hollowed through its base, and a separate, narrower chalk pillar standing a little way out to sea.", + "caption": "Photo by Chris Downer, CC BY-SA 2.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Old_Harry_and_Handfast_Point_-_geograph.org.uk_-_499665.jpg).", + "width": 640, + "height": 429 + }, + { + "src": "headland-erosion-stages.svg", + "alt": "Five panels of a rock headland in the sea: a crack, then a cave at water level, then an arch through the rock, then a stack standing alone beside a thin remnant, then a low stump.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Part of an arch, whose roof collapsed after waves hollowed a cave right through the headland", + "isCorrect": true + }, + { + "text": "An island that was always separate and has been rising out of the sea", + "isCorrect": false + }, + { + "text": "A column of harder rock that never belonged to the headland", + "isCorrect": false + }, + { + "text": "A pile of fallen blocks that the waves cemented together", + "isCorrect": false + } + ], + "explanation": "These are Old Harry Rocks on the Dorset coast in England. Waves attack a headland from both sides, forcing water and air into joints (hydraulic action) and grinding with sand and pebbles. A weak joint becomes a **cave**. Caves from each side meet, or one punches straight through, to form an **arch**. Weakened from below and weathered from above, the arch roof eventually falls, leaving the seaward end standing alone as a **stack**. Undercut at its base, the stack later topples to a **stump** covered at high tide.\n\nThis photo shows several stages at once: the big block is an arch in the making, and Old Harry himself is the stack. The chalk here once ran unbroken to the Needles on the Isle of Wight. Old Harry's original \"wife\", a neighbouring stack, is recorded as having fallen in 1509.", + "references": "[Wikipedia: Old Harry Rocks](https://en.wikipedia.org/wiki/Old_Harry_Rocks) · [Wikipedia: Stack (geology)](https://en.wikipedia.org/wiki/Stack_(geology))" + }, + { + "id": "siccar-point-unconformity", + "title": "At Siccar Point in Scotland, grey rock layers stand almost vertical, and flatter red sandstone lies across their eroded tops. Why do the two sets meet at an angle?", + "images": [ + { + "src": "siccar-point.webp", + "alt": "A rocky shore where grey rock layers stand nearly upright like ribs. Resting on their worn, flattened tops is a gently sloping cap of reddish sandstone layers.", + "caption": "Photo by Lysippos, CC BY 3.0, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Hutton%27s_unconformity_siccar_point.JPG).", + "width": 1200, + "height": 900 + }, + { + "src": "angular-unconformity-stages.svg", + "alt": "Four panels: grey layers laid down flat; the same layers tilted upright into folds; their tops eroded flat; then new red layers deposited horizontally across the eroded surface. A red line marks the eroded surface as the gap in time.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The grey layers were laid down flat, tilted upright by mountain building, worn down, and then buried under new sand much later", + "isCorrect": true + }, + { + "text": "The grey layers settled vertically against a steep underwater slope and have stayed that way ever since", + "isCorrect": false + }, + { + "text": "The red sandstone was shoved sideways over the grey rock along a low-angle fault, grinding their contact flat", + "isCorrect": false + }, + { + "text": "Both sets formed together, and the grey rock later shrank and cracked into vertical slabs", + "isCorrect": false + } + ], + "explanation": "Sediment settles in flat layers, so vertical layers mean something turned them. About 435 million years ago, mud and sand accumulated in flat beds on the floor of an ancient ocean. The ocean closed, and the collision of continents crumpled those beds until they stood nearly upright. Over the following tens of millions of years, erosion planed their tops flat. Then, around 370 million years ago, rivers and wind spread red sand across that surface. That sand became the Old Red Sandstone.\n\nThe boundary between the two, an **angular unconformity**, represents about **65 million years** missing from the record. James Hutton was shown it by boat in 1788 and took it as proof that Earth's history is almost unimaginably long. His companion John Playfair wrote that \"the mind seemed to grow giddy by looking so far into the abyss of time.\"", + "references": "[Wikipedia: Hutton's Unconformity](https://en.wikipedia.org/wiki/Hutton%27s_Unconformity) · [Wikipedia: Siccar Point](https://en.wikipedia.org/wiki/Siccar_Point) · [Wikipedia: Unconformity](https://en.wikipedia.org/wiki/Unconformity)" + }, + { + "id": "which-landforms-mean-ice", + "title": "You're walking an unfamiliar landscape. Which of these finds, taken alone, tell you that glaciers once covered it?", + "description": "Every landform here appeared earlier in this Quiz.", + "type": "multiple-choice", + "options": [ + { + "text": "A swarm of streamlined till hills, all stretched the same way", + "isCorrect": true + }, + { + "text": "A long, winding ridge of sorted sand and gravel crossing low ground", + "isCorrect": true + }, + { + "text": "A boulder of rock found nowhere in the bedrock around it", + "isCorrect": true + }, + { + "text": "A side valley whose floor ends at a cliff high above the main valley", + "isCorrect": true + }, + { + "text": "A crescent-shaped lake beside a looping river", + "isCorrect": false + }, + { + "text": "A cone of gravel spreading from a canyon mouth", + "isCorrect": false + }, + { + "text": "A straight, fresh step running across hillsides", + "isCorrect": false + } + ], + "explanation": "Drumlins, eskers, erratics and hanging valleys all need ice. Drumlins are till molded beneath moving ice. Eskers are the sediment of rivers that ran in tunnels under it. Erratics are rocks carried far from home, often uphill, which only ice does. A hanging valley records a trunk glacier deepening its floor far below its tributaries.\n\nThe other three form without ice. Oxbow lakes come from any meandering river. Alluvial fans need only a canyon, a flat plain, and occasional floods, and deserts are full of them. A straight fresh step across terrain is a fault scarp, made by an earthquake. Several of these can sit beside ice-age landforms, since rivers and faults don't stop when the ice leaves. But on their own, they say nothing about glaciers." + } + ] +} diff --git a/content/quizzes/reading-the-landscape/alluvial-fan-sorting.svg b/content/quizzes/reading-the-landscape/alluvial-fan-sorting.svg new file mode 100644 index 0000000..36d04c9 --- /dev/null +++ b/content/quizzes/reading-the-landscape/alluvial-fan-sorting.svg @@ -0,0 +1,75 @@ + + + + + The canyon mouth where rivers drop their load + + + + confined canyon: fast, deep flow + + + + + + + + + + + + + + + flow spreads out, slows, + and soaks into the gravel + boulders at the apex + sand and silt at the toe + + diff --git a/content/quizzes/reading-the-landscape/angular-unconformity-stages.svg b/content/quizzes/reading-the-landscape/angular-unconformity-stages.svg new file mode 100644 index 0000000..67dc06e --- /dev/null +++ b/content/quizzes/reading-the-landscape/angular-unconformity-stages.svg @@ -0,0 +1,105 @@ + + + + + Four events you can read in one outcrop + 1. Layers laid flat + + 2. Tilted upright + + 3. Eroded flat + + 4. New layers on top + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + the red line is the gap + At Siccar Point that gap spans about 65 million years. + + diff --git a/content/quizzes/reading-the-landscape/black-canyon-wall.webp b/content/quizzes/reading-the-landscape/black-canyon-wall.webp new file mode 100644 index 0000000..638a173 Binary files /dev/null and b/content/quizzes/reading-the-landscape/black-canyon-wall.webp differ diff --git a/content/quizzes/reading-the-landscape/borah-peak-1983.webp b/content/quizzes/reading-the-landscape/borah-peak-1983.webp new file mode 100644 index 0000000..9cbc3a2 Binary files /dev/null and b/content/quizzes/reading-the-landscape/borah-peak-1983.webp differ diff --git a/content/quizzes/reading-the-landscape/bridalveil-fall.webp b/content/quizzes/reading-the-landscape/bridalveil-fall.webp new file mode 100644 index 0000000..f77ec3e Binary files /dev/null and b/content/quizzes/reading-the-landscape/bridalveil-fall.webp differ diff --git a/content/quizzes/reading-the-landscape/cadair-idris-rock.webp b/content/quizzes/reading-the-landscape/cadair-idris-rock.webp new file mode 100644 index 0000000..7a3309a Binary files /dev/null and b/content/quizzes/reading-the-landscape/cadair-idris-rock.webp differ diff --git a/content/quizzes/reading-the-landscape/caprock-mesa-butte.svg b/content/quizzes/reading-the-landscape/caprock-mesa-butte.svg new file mode 100644 index 0000000..d4ad5c8 --- /dev/null +++ b/content/quizzes/reading-the-landscape/caprock-mesa-butte.svg @@ -0,0 +1,70 @@ + + + + + One plateau, shrinking in stages + + + + + + + + mesa + butte + spire + hard caprock + + + softer layers + below erode + slopes of weak shale collect the fallen blocks + Cliffs retreat sideways; the caprock keeps each top flat until it is gone. + + diff --git a/content/quizzes/reading-the-landscape/causeway-tops.webp b/content/quizzes/reading-the-landscape/causeway-tops.webp new file mode 100644 index 0000000..05356cd Binary files /dev/null and b/content/quizzes/reading-the-landscape/causeway-tops.webp differ diff --git a/content/quizzes/reading-the-landscape/clew-bay-island.webp b/content/quizzes/reading-the-landscape/clew-bay-island.webp new file mode 100644 index 0000000..1729123 Binary files /dev/null and b/content/quizzes/reading-the-landscape/clew-bay-island.webp differ diff --git a/content/quizzes/reading-the-landscape/columnar-jointing.svg b/content/quizzes/reading-the-landscape/columnar-jointing.svg new file mode 100644 index 0000000..db5ccdb --- /dev/null +++ b/content/quizzes/reading-the-landscape/columnar-jointing.svg @@ -0,0 +1,96 @@ + + + + + Lava that cracked as it shrank + Cooling lava flow, side view + + + + + + + + + + + + + + + + + + + + + cracks grow inward from the cold top + and base, at right angles to them + Top view + + + + + + + + + + + + + + + + + shrinking rock splits into a network; + cracks meeting near 120° release + stress most evenly, so six sides dominate + + diff --git a/content/quizzes/reading-the-landscape/cover-collapse-stages.svg b/content/quizzes/reading-the-landscape/cover-collapse-stages.svg new file mode 100644 index 0000000..c6a4a6b --- /dev/null +++ b/content/quizzes/reading-the-landscape/cover-collapse-stages.svg @@ -0,0 +1,74 @@ + + + + + How a hole appears overnight + + + 1. Void opens in limestone + + + 2. Clay arch bridges it + + + 3. Roof fails suddenly + + + + + thin arch + + + + grains wash down + into the cavity + surface looks normal + Pumping groundwater lowers the water table and speeds this up. + + diff --git a/content/quizzes/reading-the-landscape/cross-cutting-dike.svg b/content/quizzes/reading-the-landscape/cross-cutting-dike.svg new file mode 100644 index 0000000..6e40cca --- /dev/null +++ b/content/quizzes/reading-the-landscape/cross-cutting-dike.svg @@ -0,0 +1,68 @@ + + + + + Whatever cuts across is younger + + + + + + + + + + + + + 1. dark rock forms and is folded + 2. magma forces into cracks across it + younger dike + + diff --git a/content/quizzes/reading-the-landscape/death-valley-canyon-mouth.webp b/content/quizzes/reading-the-landscape/death-valley-canyon-mouth.webp new file mode 100644 index 0000000..f626350 Binary files /dev/null and b/content/quizzes/reading-the-landscape/death-valley-canyon-mouth.webp differ diff --git a/content/quizzes/reading-the-landscape/december-giant.webp b/content/quizzes/reading-the-landscape/december-giant.webp new file mode 100644 index 0000000..9db9cb2 Binary files /dev/null and b/content/quizzes/reading-the-landscape/december-giant.webp differ diff --git a/content/quizzes/reading-the-landscape/drumlin-stoss-lee.svg b/content/quizzes/reading-the-landscape/drumlin-stoss-lee.svg new file mode 100644 index 0000000..0b96424 --- /dev/null +++ b/content/quizzes/reading-the-landscape/drumlin-stoss-lee.svg @@ -0,0 +1,70 @@ + + + + + A hill molded under moving ice + + + + + + + + + + + ice flow + steep, blunt end + up-ice: + long tapering tail + down-ice: + till: clay, sand and stones mixed + A drumlin is the reverse of a roche moutonnée: its steep end faces the ice. + + diff --git a/content/quizzes/reading-the-landscape/dune-cross-bedding.svg b/content/quizzes/reading-the-landscape/dune-cross-bedding.svg new file mode 100644 index 0000000..e8016bc --- /dev/null +++ b/content/quizzes/reading-the-landscape/dune-cross-bedding.svg @@ -0,0 +1,93 @@ + + + + + Fossil dunes still point the way the wind blew + + + wind + + + + + + + + + grains hop up the gentle side + they avalanche down + the steep lee face + (about 30–34°) + + + + + + + + + + + + + + + + + + + + + + + + in rock: stacked sets of inclined layers, all dipping downwind + Each set's top was planed off by the next dune passing over. + + diff --git a/content/quizzes/reading-the-landscape/esker-tunnel-cast.svg b/content/quizzes/reading-the-landscape/esker-tunnel-cast.svg new file mode 100644 index 0000000..7529634 --- /dev/null +++ b/content/quizzes/reading-the-landscape/esker-tunnel-cast.svg @@ -0,0 +1,71 @@ + + + + + A river channel cast in gravel + Under the ice + + + + meltwater tunnel + stream fills its tunnel with + sorted, layered sand and gravel + After the ice melts + + + + + + esker: a winding ridge + the gravel that filled the tunnel + now stands in the open + + + + diff --git a/content/quizzes/reading-the-landscape/glacial-valley-glengesh.webp b/content/quizzes/reading-the-landscape/glacial-valley-glengesh.webp new file mode 100644 index 0000000..675adb9 Binary files /dev/null and b/content/quizzes/reading-the-landscape/glacial-valley-glengesh.webp differ diff --git a/content/quizzes/reading-the-landscape/hanging-valley-step.svg b/content/quizzes/reading-the-landscape/hanging-valley-step.svg new file mode 100644 index 0000000..787c4eb --- /dev/null +++ b/content/quizzes/reading-the-landscape/hanging-valley-step.svg @@ -0,0 +1,69 @@ + + + + + Why the side valley is left hanging + + + + + small tributary glacier + + shallow floor + large trunk glacier + deep floor + + after the ice melts: + the tributary stream falls + off the step as a waterfall + + + Both glacier surfaces met at one level; their floors did not. + + diff --git a/content/quizzes/reading-the-landscape/headland-erosion-stages.svg b/content/quizzes/reading-the-landscape/headland-erosion-stages.svg new file mode 100644 index 0000000..22f0317 --- /dev/null +++ b/content/quizzes/reading-the-landscape/headland-erosion-stages.svg @@ -0,0 +1,73 @@ + + + + + A headland wears away in a fixed order + 1. crack + + 2. cave + + 3. arch + + 4. stack + + 5. stump + + + + + + + + + + Waves exploit a joint, hollow it out, punch through, and the roof falls. + The same headland can show several stages at once. + + diff --git a/content/quizzes/reading-the-landscape/karst-underground-drainage.svg b/content/quizzes/reading-the-landscape/karst-underground-drainage.svg new file mode 100644 index 0000000..103f0f4 --- /dev/null +++ b/content/quizzes/reading-the-landscape/karst-underground-drainage.svg @@ -0,0 +1,76 @@ + + + + + Where the river went + + + + + + + + + + + + stream on the moor + + + sinks into cracks and follows + channels it dissolved + emerges at the cliff foot + limestone: rain with dissolved + CO₂ is a weak acid that + widens joints into channels + dry lip: an old waterfall, + now above the water's route + In limestone the drainage moves underground, leaving dry valleys above. + + diff --git a/content/quizzes/reading-the-landscape/ledyard-boulder.webp b/content/quizzes/reading-the-landscape/ledyard-boulder.webp new file mode 100644 index 0000000..16402a8 Binary files /dev/null and b/content/quizzes/reading-the-landscape/ledyard-boulder.webp differ diff --git a/content/quizzes/reading-the-landscape/malham-cove.webp b/content/quizzes/reading-the-landscape/malham-cove.webp new file mode 100644 index 0000000..9118c27 Binary files /dev/null and b/content/quizzes/reading-the-landscape/malham-cove.webp differ diff --git a/content/quizzes/reading-the-landscape/meander-cross-section.svg b/content/quizzes/reading-the-landscape/meander-cross-section.svg new file mode 100644 index 0000000..86384f2 --- /dev/null +++ b/content/quizzes/reading-the-landscape/meander-cross-section.svg @@ -0,0 +1,66 @@ + + + + + One bend, two different banks + + + + outer bank + cut bank: + undercut, + collapses + deepest, fastest water + inner bank + point bar: sand and + gravel dropped in + slow water + The bend migrates outward: erosion on one bank, deposition on the other. + + diff --git a/content/quizzes/reading-the-landscape/normal-fault-scarp.svg b/content/quizzes/reading-the-landscape/normal-fault-scarp.svg new file mode 100644 index 0000000..81e9996 --- /dev/null +++ b/content/quizzes/reading-the-landscape/normal-fault-scarp.svg @@ -0,0 +1,78 @@ + + + + + A step made in seconds + + + + + + + + + + + scarp: fresh step + at the surface + + + + + block drops down + + + + + crust pulled apart + fault plane + footwall + hanging wall + + diff --git a/content/quizzes/reading-the-landscape/numata-relief.webp b/content/quizzes/reading-the-landscape/numata-relief.webp new file mode 100644 index 0000000..6a8d87d Binary files /dev/null and b/content/quizzes/reading-the-landscape/numata-relief.webp differ diff --git a/content/quizzes/reading-the-landscape/old-harry.webp b/content/quizzes/reading-the-landscape/old-harry.webp new file mode 100644 index 0000000..199e898 Binary files /dev/null and b/content/quizzes/reading-the-landscape/old-harry.webp differ diff --git a/content/quizzes/reading-the-landscape/oxbow-cutoff-stages.svg b/content/quizzes/reading-the-landscape/oxbow-cutoff-stages.svg new file mode 100644 index 0000000..a38d3ed --- /dev/null +++ b/content/quizzes/reading-the-landscape/oxbow-cutoff-stages.svg @@ -0,0 +1,79 @@ + + + + + How a river leaves a lake behind + 1. Loop tightens + 2. Flood breaks the neck + 3. Ends silt up + + + + + + + outer banks erode + toward each other + + + + + water takes the + short cut + + + + + + + sediment seals the ends: + a crescent lake + The abandoned loop keeps the curve of the old channel. + + diff --git a/content/quizzes/reading-the-landscape/punkaharju-ridge.webp b/content/quizzes/reading-the-landscape/punkaharju-ridge.webp new file mode 100644 index 0000000..b1d389b Binary files /dev/null and b/content/quizzes/reading-the-landscape/punkaharju-ridge.webp differ diff --git a/content/quizzes/reading-the-landscape/red-tarn-striding-edge.webp b/content/quizzes/reading-the-landscape/red-tarn-striding-edge.webp new file mode 100644 index 0000000..3e6402a Binary files /dev/null and b/content/quizzes/reading-the-landscape/red-tarn-striding-edge.webp differ diff --git a/content/quizzes/reading-the-landscape/roche-moutonnee-stoss-lee.svg b/content/quizzes/reading-the-landscape/roche-moutonnee-stoss-lee.svg new file mode 100644 index 0000000..da55538 --- /dev/null +++ b/content/quizzes/reading-the-landscape/roche-moutonnee-stoss-lee.svg @@ -0,0 +1,65 @@ + + + + + Reading ice direction from bare rock + + + + + ice flow + stoss side: smoothed and scratched + by debris dragged across it + lee side: steep, jagged + blocks plucked away + + + Gentle, polished face points up-ice; broken face points down-ice. + + diff --git a/content/quizzes/reading-the-landscape/siccar-point.webp b/content/quizzes/reading-the-landscape/siccar-point.webp new file mode 100644 index 0000000..7c8620b Binary files /dev/null and b/content/quizzes/reading-the-landscape/siccar-point.webp differ diff --git a/content/quizzes/reading-the-landscape/skagit-bend.webp b/content/quizzes/reading-the-landscape/skagit-bend.webp new file mode 100644 index 0000000..9eb42b4 Binary files /dev/null and b/content/quizzes/reading-the-landscape/skagit-bend.webp differ diff --git a/content/quizzes/reading-the-landscape/spiti-slope.webp b/content/quizzes/reading-the-landscape/spiti-slope.webp new file mode 100644 index 0000000..d81aa68 Binary files /dev/null and b/content/quizzes/reading-the-landscape/spiti-slope.webp differ diff --git a/content/quizzes/reading-the-landscape/spring-line-contact.svg b/content/quizzes/reading-the-landscape/spring-line-contact.svg new file mode 100644 index 0000000..bc11cde --- /dev/null +++ b/content/quizzes/reading-the-landscape/spring-line-contact.svg @@ -0,0 +1,78 @@ + + + + + Why springs line up on one level + + + + + + + + + porous sandstone: water soaks down + less permeable siltstone and mudstone + + + + + + + + + + + + seeps emerge along the contact + the alcove grows here as wet + rock crumbles out + Water sinks until it meets a layer it cannot easily enter, then moves sideways. + + diff --git a/content/quizzes/reading-the-landscape/tasman-moraine-slope.webp b/content/quizzes/reading-the-landscape/tasman-moraine-slope.webp new file mode 100644 index 0000000..55db7bd Binary files /dev/null and b/content/quizzes/reading-the-landscape/tasman-moraine-slope.webp differ diff --git a/content/quizzes/reading-the-landscape/terrace-flight-ages.svg b/content/quizzes/reading-the-landscape/terrace-flight-ages.svg new file mode 100644 index 0000000..29091ae --- /dev/null +++ b/content/quizzes/reading-the-landscape/terrace-flight-ages.svg @@ -0,0 +1,67 @@ + + + + + Stairs a river cut into its own valley + + + + + + + + + oldest + younger + younger still + today's river + Each flat step is an old floodplain, left behind when the river cut down. + Higher means older: the opposite of stacked layers. + + diff --git a/content/quizzes/reading-the-landscape/u-versus-v-valley.svg b/content/quizzes/reading-the-landscape/u-versus-v-valley.svg new file mode 100644 index 0000000..7c6e1c8 --- /dev/null +++ b/content/quizzes/reading-the-landscape/u-versus-v-valley.svg @@ -0,0 +1,65 @@ + + + + + A river cuts a V, a glacier a U + River valley + + + V: the river cuts only its bed; slopes slump in + Glaciated valley + + + + ice fills the whole valley + U: ice grinds the floor and both walls at once + + trimline: the highest the ice reached + + diff --git a/content/quizzes/reading-the-landscape/weeping-rock.webp b/content/quizzes/reading-the-landscape/weeping-rock.webp new file mode 100644 index 0000000..678339f Binary files /dev/null and b/content/quizzes/reading-the-landscape/weeping-rock.webp differ diff --git a/content/quizzes/reading-the-landscape/west-mitten.webp b/content/quizzes/reading-the-landscape/west-mitten.webp new file mode 100644 index 0000000..7ebd0db Binary files /dev/null and b/content/quizzes/reading-the-landscape/west-mitten.webp differ diff --git a/content/quizzes/reading-the-landscape/yamal-river.webp b/content/quizzes/reading-the-landscape/yamal-river.webp new file mode 100644 index 0000000..58ceb54 Binary files /dev/null and b/content/quizzes/reading-the-landscape/yamal-river.webp differ diff --git a/content/quizzes/reading-the-landscape/zion-sandstone.webp b/content/quizzes/reading-the-landscape/zion-sandstone.webp new file mode 100644 index 0000000..980a81d Binary files /dev/null and b/content/quizzes/reading-the-landscape/zion-sandstone.webp differ diff --git a/content/quizzes/silicon-valley-became-real.json b/content/quizzes/silicon-valley-became-real.json new file mode 100644 index 0000000..1f44fef --- /dev/null +++ b/content/quizzes/silicon-valley-became-real.json @@ -0,0 +1,384 @@ +{ + "schemaVersion": 1, + "id": "silicon-valley-became-real", + "title": "When HBO's Silicon Valley became real: Not Hotdog, the Weissman score and tabs versus spaces", + "description": "HBO's *Silicon Valley* hired tech consultants so its jokes would hold up, and a few of those jokes escaped into the real world: a compression metric a Stanford professor invented for the show, a hotdog-detecting app you could actually install, and a fake Google that the real Google linked to. Each Question starts from a scene and asks what happened next in real life. The Explanations cover the real tech behind the jokes: transfer learning, class imbalance, and why a score built on logarithms depends on your stopwatch.", + "language": "en", + "tags": ["compression", "statistics", "internet-culture"], + "author": "Quizbun", + "questions": [ + { + "id": "where-weissman-came-from", + "title": "Pied Piper's compression is judged by its \"Weissman score\". Where did that metric come from?", + "type": "single-choice", + "options": [ + { + "text": "A Stanford professor and his PhD student invented it for the show, at the producers' request", + "isCorrect": true + }, + { + "text": "It's a standard compression benchmark from the 1990s that the writers borrowed", + "isCorrect": false + }, + { + "text": "The writers made up the name, and no formula behind it exists", + "isCorrect": false + }, + { + "text": "Dropbox engineers used it internally, and a consultant brought it to the show", + "isCorrect": false + } + ], + "explanation": "The producers wanted Pied Piper's breakthrough to sound credible, so the show's tech adviser, Jonathan Dotan, went looking for a compression expert. He found **Tsachy Weissman**, an information theory professor at Stanford, who brought in his PhD student **Vinith Misra**. Together they designed the fictional algorithm and a metric to score it, and the metric took Weissman's name.\n\nThe show needed one number an audience could follow. Real compression researchers report at least two: how small the output gets (the **compression ratio**) and how long it takes. The Weissman score folds both into a single value, compared against a standard compressor for the same kind of data, such as FLAC for audio.\n\nThe Dropbox option gets the order backwards. Dropbox engineers did use the score, but in 2016, two years after the show invented it. A later Question covers that.", + "references": "[IEEE Spectrum: A Fictional Compression Metric Moves Into the Real World (2014)](https://spectrum.ieee.org/a-madefortv-compression-metric-moves-to-the-real-world), [IEEE Spectrum: A Made-For-TV Compression Algorithm (2014)](https://spectrum.ieee.org/a-madefortv-compression-algorithm), [Wikipedia: Weissman score](https://en.wikipedia.org/wiki/Weissman_score)" + }, + { + "id": "compute-a-weissman-score", + "title": "Compute the Weissman score for this compressor.", + "description": "Take α = 1. Your compressor reaches a compression ratio of r = 4, and the standard compressor reaches r̄ = 2. Yours takes T = 10 seconds, and the standard takes T̄ = 100 seconds. Use logarithms in any base.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [4] + }, + "explanation": "Work through it one term at a time.\n\n- **Ratio term:** r / r̄ = 4 / 2 = 2. Your output is half the size of the standard's.\n- **Time term:** log T̄ / log T = log 100 / log 10. In base 10 that's 2 / 1 = 2. Any base gives the same answer, because a change of base multiplies the top and bottom by the same factor.\n- **Score:** W = 1 × 2 × 2 = **4**.\n\nThe two terms treat their inputs differently. Doubling the compression ratio doubles the ratio term. Being ten times faster only moved the time term from 1 to 2, because the logarithm squashes large differences in time. That squashing also makes the score behave strangely, as the next Question shows.", + "references": "[Wikipedia: Weissman score](https://en.wikipedia.org/wiki/Weissman_score)", + "images": [ + { + "src": "weissman-formula.svg", + "alt": "The formula W equals alpha times r over r-bar times log T-bar over log T. Alpha is labelled scaling constant. The ratio term r over r-bar is labelled: r is your compression ratio, r-bar is a standard compressor's. The time term log T-bar over log T is labelled: T is your time to compress, T-bar is the standard's time. A note says a higher W is better, and a bigger ratio or a shorter time raises it.", + "caption": "Formula as given in IEEE Spectrum and Wikipedia. Diagram drawn for this Quiz.", + "width": 720, + "height": 360 + } + ] + }, + { + "id": "weissman-units-problem", + "title": "You score the same two compressors with times in minutes, then in seconds, and get two different Weissman scores. What does that tell you?", + "description": "The standard compressor reaches a ratio of 2.1 in 2 minutes. Yours reaches 3.4 in 3 minutes. With α = 1:\n\n- In minutes: W = (3.4 / 2.1) × (log 2 / log 3) ≈ **1.02**\n- In seconds: W = (3.4 / 2.1) × (log 120 / log 180) ≈ **1.49**", + "type": "single-choice", + "options": [ + { + "text": "The score depends on the unit of time, because taking the log of a time doesn't cancel its unit", + "isCorrect": true + }, + { + "text": "Seconds are more precise than minutes, so 1.49 is the correct score", + "isCorrect": false + }, + { + "text": "It's a rounding error that disappears if you keep more decimal places", + "isCorrect": false + }, + { + "text": "One of the two calculations must have used the wrong logarithm base", + "isCorrect": false + } + ], + "explanation": "Both calculations are correct, and the difference is real. A ratio of two times, such as 120 s / 180 s, is the same in any unit, because the units cancel. A ratio of two *logarithms* of times doesn't cancel. log 2 / log 3 and log 120 / log 180 are different numbers, even though they describe the same stopwatch readings.\n\nIt gets worse near 1. If a time comes out as exactly 1 unit, its logarithm is 0, and the score becomes 0 or infinite. Below 1 unit, the logarithm turns negative, and a faster compressor can end up with a *negative* score.\n\nThe base of the logarithm doesn't cause this. Changing the base multiplies every logarithm by the same constant, so the ratio of two logarithms stays the same. That's why the previous Question could accept any base.\n\nThe same check works on any single number that sums up performance. Change only the units, and if the number moves, be careful with it.", + "references": "[Wikipedia: Weissman score, Limitations](https://en.wikipedia.org/wiki/Weissman_score#Limitations), [Wikipedia: Dimensionless quantity](https://en.wikipedia.org/wiki/Dimensionless_quantity)" + }, + { + "id": "dropbox-weissman-score", + "title": "In 2016, two Dropbox engineers reported a real Weissman score in a post on the company's tech blog. What had they built?", + "type": "single-choice", + "options": [ + { + "text": "A Brotli compressor written in Rust", + "isCorrect": true + }, + { + "text": "Lepton, a tool that shrinks JPEG photos", + "isCorrect": false + }, + { + "text": "A working middle-out compression algorithm", + "isCorrect": false + }, + { + "text": "A video codec for Dropbox's file previews", + "isCorrect": false + } + ], + "explanation": "Daniel Reiter Horn and Mehant Baid wrote about re-implementing **Brotli**, Google's general-purpose compression format, in **Rust**. They picked Rust because a decompressor reads untrusted files from users. Rust prevents the memory errors that let a malformed file become a security hole, and it does so without the pauses of a garbage collector.\n\nThe post opens with a nod to Pied Piper. It reports that their changes raised Brotli's Weissman score by 6.5% and adds that Richard Hendricks would be gleeful. The authors also noted that the score \"favors compression speed over ratio in most cases\", one more quirk to add to the previous Questions.\n\nLepton is a tempting pick because it's also a real Dropbox compression project from 2016. It rewrites JPEG files about 22% smaller and can restore the original bit for bit. Its published results report storage saved, not a Weissman score.", + "references": "[Dropbox Tech Blog: Lossless compression with Brotli in Rust for a bit of Pied Piper on the backend (2016)](https://dropbox.tech/infrastructure/lossless-compression-with-brotli), [Dropbox Blog: Developing Lepton at Dropbox](https://blog.dropbox.com/topics/technology/developing-lepton-at-dropbox). For what compression ratios really mean, see the Quiz \"Compression: what actually shrinks\"." + }, + { + "id": "hooli-in-the-alphabet-letter", + "title": "In August 2015, Google announced its new parent company, Alphabet, on the website abc.xyz. What was hidden in the announcement?", + "type": "single-choice", + "options": [ + { + "text": "A link to hooli.xyz, the website of the show's fake Google", + "isCorrect": true + }, + { + "text": "A Weissman score for Google's own compression tools", + "isCorrect": false + }, + { + "text": "A job advert for Pied Piper engineers", + "isCorrect": false + }, + { + "text": "A countdown to the next season of Silicon Valley", + "isCorrect": false + } + ], + "explanation": "Hooli is the show's parody of Google: a giant with a campus, a guru and a founder, Gavin Belson, who talks about making the world a better place. HBO ran a real website for it at **hooli.xyz**.\n\nOn 10 August 2015, Larry Page posted a letter at abc.xyz announcing Alphabet. The period at the end of one sentence was a link, and clicking it went to hooli.xyz. The show's executive producer, Alec Berg, joked afterwards that they had founded Google as a setup for this one joke.\n\nThe joke worked because of the domain. Alphabet couldn't get alphabet.com, so it used abc.xyz, and the show had already put Hooli on the same unusual `.xyz` ending.", + "references": "[The Register: Google's new parent Alphabet owns abc.xyz (2015)](https://www.theregister.com/2015/08/11/google_parent_hooli/), [The Hollywood Reporter: Google's hidden 'Silicon Valley' joke, show producer responds (2015)](https://www.hollywoodreporter.com/tv/tv-news/googles-hidden-silicon-valley-joke-814375/)" + }, + { + "id": "tabs-versus-spaces-breakup", + "title": "In \"Bachmanity Insanity\" (season 3, episode 6, 2016), Richard breaks up with Winnie, a developer from Facebook. What does he refuse to put up with?", + "type": "single-choice", + "options": [ + { + "text": "She indents her code with spaces instead of tabs", + "isCorrect": true + }, + { + "text": "She prefers Emacs to Vim", + "isCorrect": false + }, + { + "text": "She pushes code without tests", + "isCorrect": false + }, + { + "text": "She writes JavaScript without semicolons", + "isCorrect": false + } + ], + "explanation": "Richard is a committed tabs user. Gilfoyle and Dinesh notice that Winnie uses spaces and warn her not to tell him. When they sit and code side by side, the sound of her tapping the space bar is more than he can take, and the relationship ends.\n\nThe argument is older than the show. A **tab** is one character, and each editor decides how wide to draw it, so every reader can pick their own indent width. **Spaces** look identical everywhere, which keeps aligned code aligned. Many people who \"use spaces\" still press the Tab key, and their editor inserts the spaces for them. Hardly anyone taps the space bar the way Winnie does.\n\nToday most teams let a tool settle it. Go's `gofmt` indents with tabs, Python's PEP 8 style guide recommends spaces, and Prettier uses spaces unless you set `useTabs`.\n\nA year after the episode, Stack Overflow's 2017 survey found that developers who used spaces reported higher median salaries. TheWrap ran it as a fact check headlined \"Richard is wrong\". What that data can and can't show is the subject of the Quiz \"Tabs, spaces and Vim: developer holy wars, measured\".", + "references": "[Wikipedia: Silicon Valley season 3](https://en.wikipedia.org/wiki/Silicon_Valley_season_3), [TheWrap: 'Silicon Valley' fact check: Richard is wrong about tabs versus spaces (2017)](https://www.thewrap.com/silicon-valley-fact-check-why-richard-is-wrong-on-tabs-versus-spaces/), [PEP 8: Tabs or Spaces?](https://peps.python.org/pep-0008/#tabs-or-spaces)" + }, + { + "id": "not-hotdog-was-real", + "title": "In \"Teambuilding Exercise\" (season 4, episode 4, 2017), Jian-Yang demos SeeFood, an app meant to recognize any food. It can only say \"hotdog\" or \"not hotdog\". What happened in the real world?", + "type": "single-choice", + "options": [ + { + "text": "HBO released a working Not Hotdog app that did exactly that", + "isCorrect": true + }, + { + "text": "Nothing: the phone screens in the scene were mock-ups added in editing", + "isCorrect": false + }, + { + "text": "Google published it as an official TensorFlow demo", + "isCorrect": false + }, + { + "text": "Fans built a copy, and HBO had it taken down", + "isCorrect": false + } + ], + "explanation": "The show released **Not Hotdog** as a real app. Point your phone at something and it tells you whether it's a hotdog. The show built it in-house, and it really ran a neural network trained for that one yes-or-no choice.\n\nThe joke pokes at a familiar pattern. A startup promises general-purpose AI and ships a classifier with two answers. Even those two answers took real engineering, which the next Questions follow step by step.\n\nThe app, along with the show's other interactive tie-ins, earned a 2018 Emmy nomination for Outstanding Creative Achievement in Interactive Media within a Scripted Program.", + "references": "[Tim Anglade: How HBO's Silicon Valley built \"Not Hotdog\" with mobile TensorFlow, Keras & React Native (2017)](https://medium.com/@timanglade/how-hbos-silicon-valley-built-not-hotdog-with-mobile-tensorflow-keras-react-native-ef03260747f3), [Television Academy: 2018 nominees, Outstanding Creative Achievement in Interactive Media within a Scripted Program](https://www.televisionacademy.com/awards/nominees-winners/2018/outstanding-creative-achievement-in-interactive-media-within-a-scripted-program)", + "videos": [ + { + "provider": "youtube", + "id": "ACmydtFDTGs", + "placement": "explanation" + } + ] + }, + { + "id": "who-built-not-hotdog", + "title": "Who built the real Not Hotdog app?", + "type": "single-choice", + "options": [ + { + "text": "One developer working for the show, Tim Anglade, training on a laptop with an external graphics card", + "isCorrect": true + }, + { + "text": "A team from Google's TensorFlow group, on Google's cloud servers", + "isCorrect": false + }, + { + "text": "A Stanford AI lab, recommended by the show's compression consultants", + "isCorrect": false + }, + { + "text": "An outside app agency with a dozen engineers", + "isCorrect": false + } + ], + "explanation": "Tim Anglade built it in-house for the show. By his account, it was one developer with one laptop, a 2015 MacBook Pro, an external Nvidia GPU and hand-picked training data. The final model trained for about 80 hours on that setup.\n\nBy 2017, a useful image classifier no longer needed a research lab or a data center. One person could get one working with open frameworks such as TensorFlow and Keras, published network designs and a gaming graphics card.\n\nThe Stanford option mixes up two stories. Stanford researchers helped with the show's compression storyline, not with the app.", + "references": "[Tim Anglade: How HBO's Silicon Valley built \"Not Hotdog\" (2017)](https://medium.com/@timanglade/how-hbos-silicon-valley-built-not-hotdog-with-mobile-tensorflow-keras-react-native-ef03260747f3), [TechCrunch: The engineer behind the Not Hotdog app put a lot of thought into AI (2017)](https://techcrunch.com/2017/06/28/thinking-about-hotdogs/)" + }, + { + "id": "why-run-on-the-phone", + "title": "Anglade's first version sent photos to a cloud vision API. Which reasons did he give for moving the neural network onto the phone?", + "type": "multiple-choice", + "options": [ + { + "text": "It's faster, because there's no round trip to a server", + "isCorrect": true + }, + { + "text": "It keeps working with no internet connection", + "isCorrect": true + }, + { + "text": "Photos never leave the user's phone", + "isCorrect": true + }, + { + "text": "It costs nothing to run per photo, however many people use it", + "isCorrect": true + }, + { + "text": "A phone's processor classifies images more accurately than a server", + "isCorrect": false + }, + { + "text": "App stores don't allow apps to upload photos to a server", + "isCorrect": false + } + ], + "explanation": "All four reasons appear in Anglade's write-up. Running the model on the phone itself is called **on-device inference**. A cloud API means every photo travels to a server and back, which adds latency, fails without a network, sends a stranger's photos off the phone, and bills you per request. A viral app with a joke budget can't afford that last one.\n\nThe early cloud version had another problem. A general-purpose vision API wasn't accurate enough at this one narrow job. That doesn't make phones more accurate. A server can run a much bigger model than a phone can. What helped was a small model trained for exactly this task.\n\nApp stores allow photo uploads, and plenty of apps do it. The real catch with on-device inference is that the model has to fit on the phone, as the next Question shows.", + "references": "[Tim Anglade: How HBO's Silicon Valley built \"Not Hotdog\" (2017)](https://medium.com/@timanglade/how-hbos-silicon-valley-built-not-hotdog-with-mobile-tensorflow-keras-react-native-ef03260747f3)" + }, + { + "id": "transfer-learning-from-inception", + "title": "Anglade started with only a few thousand hotdog photos, far too few to teach a network to see from nothing. How did he get a working first model?", + "type": "single-choice", + "options": [ + { + "text": "He took Inception, a network already trained on a huge collection of general photos, and retrained it on his hotdog data", + "isCorrect": true + }, + { + "text": "He trained a brand-new network from random weights on the hotdog photos alone", + "isCorrect": false + }, + { + "text": "He wrote rules by hand for the shape and color of a sausage in a bun", + "isCorrect": false + }, + { + "text": "He had the show's crew label a million new photos", + "isCorrect": false + } + ], + "explanation": "This is **transfer learning**. A network trained on a broad image collection has already learned the general parts of vision: edges, textures, curves, parts of objects. Those features are useful for almost any photo. You keep them and retrain only the final layers, which decide what the features mean for your task. A few thousand examples are enough for that.\n\nTraining from random weights would have to learn vision itself from a few thousand photos, and it would mostly memorize them. Hand-written rules break on the first hotdog photographed from above, in a different bun, or under yellow light. Neural networks took over image recognition because nobody could write those rules.\n\nMost applied machine learning works this way today. Teams rarely train from scratch. They start from a pretrained model and adapt it.", + "references": "[Tim Anglade: How HBO's Silicon Valley built \"Not Hotdog\" (2017)](https://medium.com/@timanglade/how-hbos-silicon-valley-built-not-hotdog-with-mobile-tensorflow-keras-react-native-ef03260747f3), [TensorFlow: Transfer learning and fine-tuning](https://www.tensorflow.org/tutorials/images/transfer_learning)", + "images": [ + { + "src": "not-hotdog-pipeline.svg", + "alt": "Two-row diagram titled train on a laptop, then run on the phone. Top row, training on a laptop with an external GPU: a pretrained network that already knows edges, textures and shapes, then retraining on food photos, 3,000 hotdogs and 147,000 not hotdogs, then a small MobileNets-style model file of about 4 million parameters, which ships inside the app. Bottom row, on the phone with no server involved: a camera photo that is never uploaded goes into the neural network running on the phone, which outputs hotdog or not hotdog. A note lists the benefits: faster with no round trip, works offline, photos stay private, and no server bill per photo.", + "caption": "Based on Tim Anglade's 2017 write-up. Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 380 + } + ] + }, + { + "id": "why-not-ship-inception", + "title": "The Inception-based model worked, but it didn't ship in the app. Why not?", + "type": "single-choice", + "options": [ + { + "text": "It was too big for a phone: about 23 million parameters used too much memory to run reliably", + "isCorrect": true + }, + { + "text": "It couldn't tell a hotdog from a plain sausage", + "isCorrect": false + }, + { + "text": "Its license didn't allow use in a commercial app", + "isCorrect": false + }, + { + "text": "It needed an internet connection to run", + "isCorrect": false + } + ], + "explanation": "Every parameter is a number the phone has to hold in memory and multiply on each photo. Inception has about 23 million. On a phone, that meant crashes and slow answers.\n\nAnglade tried **SqueezeNet** next, with about 1.25 million parameters. It fit, but it struggled to learn what a hotdog is. The model that shipped was his own design based on Google's **MobileNets**, with about 4 million parameters. MobileNets split each expensive convolution into two cheaper steps. That keeps much of the accuracy of a big network at a fraction of the size and compute.\n\nThis trade-off comes up all the time in mobile machine learning. You rarely ship the most accurate model. You ship the most accurate one that fits the device's memory, battery and time budget.", + "references": "[Tim Anglade: How HBO's Silicon Valley built \"Not Hotdog\" (2017)](https://medium.com/@timanglade/how-hbos-silicon-valley-built-not-hotdog-with-mobile-tensorflow-keras-react-native-ef03260747f3), [arXiv: MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications (Howard et al., 2017)](https://arxiv.org/abs/1704.04861)" + }, + { + "id": "red-sauce-shortcut", + "title": "One of Anglade's smaller models seemed to decide \"red sauce means hotdog\". What went wrong?", + "type": "single-choice", + "options": [ + { + "text": "It learned a shortcut: red sauce often appeared with hotdogs in its training photos, so it used the sauce instead of the hotdog", + "isCorrect": true + }, + { + "text": "The phone camera shifted colors toward red, so everything looked like ketchup", + "isCorrect": false + }, + { + "text": "It memorized the exact training photos, so it recognized only ketchup it had seen before", + "isCorrect": false + }, + { + "text": "The training labels had been swapped, so hotdogs were marked as ketchup", + "isCorrect": false + } + ], + "explanation": "A classifier learns whatever separates the labels in its training data most easily. If many hotdog photos have ketchup and few other photos have red sauce, \"red sauce\" separates the classes well on that data. It's cheaper to learn than the shape of a sausage in a bun. TechCrunch described it as the model using bad heuristics as a crutch.\n\nThis is called **shortcut learning**, or learning a **spurious correlation**. The model scores well in testing if the test photos share the same bias, then fails on a plate of spaghetti. The fix is in the data: add photos that break the shortcut, such as red sauce with no hotdog and hotdogs with no sauce, so the easy feature stops working.\n\nMemorizing photos, the **overfitting** option, is a real and related problem. But a model that only memorized would fail on *every* new photo. This one did generalize. It just learned the wrong rule.", + "references": "[TechCrunch: The engineer behind the Not Hotdog app put a lot of thought into AI (2017)](https://techcrunch.com/2017/06/28/thinking-about-hotdogs/), [Geirhos et al.: Shortcut learning in deep neural networks (Nature Machine Intelligence, 2020)](https://arxiv.org/abs/2004.07780)" + }, + { + "id": "always-not-hotdog-accuracy", + "title": "The final training set had 150,000 photos, of which 3,000 were hotdogs. A lazy model answers \"not hotdog\" for every photo. What accuracy does it score on this set, in percent?", + "description": "Type a number without the `%` sign.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [98] + }, + "explanation": "It gets all 147,000 not-hotdogs right and all 3,000 hotdogs wrong: 147,000 / 150,000 = **98%**. That's an impressive-looking score for a model that has never found a single hotdog.\n\nAnglade chose the ratio on purpose, 49 not-hotdogs for every hotdog, because most things people point a phone at aren't hotdogs. That also makes \"not hotdog\" the hard class. It isn't one thing. It has to cover pizza, shoes, sausages without buns, and everything else in the world.\n\nWhen one class is rare, accuracy hides failure. Ask two better questions instead. Of the real hotdogs, how many did the model find? That's **recall**. Of the photos it called hotdogs, how many really were? That's **precision**. Fraud detection, disease screening and spam filters all have the same imbalance and the same trap.", + "references": "[Tim Anglade: How HBO's Silicon Valley built \"Not Hotdog\" (2017)](https://medium.com/@timanglade/how-hbos-silicon-valley-built-not-hotdog-with-mobile-tensorflow-keras-react-native-ef03260747f3), [Google Machine Learning Crash Course: Accuracy, precision and recall](https://developers.google.com/machine-learning/crash-course/classification/accuracy-precision-recall)", + "images": [ + { + "src": "not-hotdog-class-balance.svg", + "alt": "A single bar split into two parts. Not hotdog: 147,000 photos, 98 percent, filling almost the whole bar. Hotdog: 3,000 photos, 2 percent, a thin sliver at the end. Below: a model that always answers not hotdog is right on all 147,000 not-hotdogs and wrong on all 3,000 hotdogs, giving 98 percent accuracy without ever finding a hotdog.", + "caption": "Counts from Tim Anglade's 2017 write-up. Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 330 + } + ] + }, + { + "id": "which-became-real", + "title": "Which of these from Silicon Valley exist, or happened, in the real world?", + "type": "multiple-choice", + "options": [ + { + "text": "A Not Hotdog app you could install on your phone", + "isCorrect": true + }, + { + "text": "Engineers outside the show reporting Weissman scores", + "isCorrect": true + }, + { + "text": "A hooli.xyz website, linked from Google's own Alphabet announcement", + "isCorrect": true + }, + { + "text": "Middle-out compression, working and beating existing compressors", + "isCorrect": false + }, + { + "text": "Pied Piper's record Weissman score of 5.2, measured on real software", + "isCorrect": false + } + ], + "explanation": "The app, the metric and the website all crossed over. Not Hotdog shipped, Dropbox engineers reported a Weissman score for their Rust Brotli compressor, and Larry Page's Alphabet letter hid a link to hooli.xyz.\n\n**Middle-out** stayed fiction on purpose. Vinith Misra told IEEE Spectrum that the writers needed an approach that isn't possible today, but where it isn't *obviously* impossible. The show describes it as working outward from the middle of the data to find hidden structure. That's plausible enough to sound like research, and too vague to implement.\n\nThe 5.2 score comes from the season 1 finale, when Pied Piper doubles the best score on record at TechCrunch Disrupt. It was computed for a fictional algorithm on a fictional file. And as the units Question showed, a Weissman score only means something if you know the standard compressor and the unit of time behind it.", + "references": "[IEEE Spectrum: A Made-For-TV Compression Algorithm (2014)](https://spectrum.ieee.org/a-madefortv-compression-algorithm), [Dropbox Tech Blog: Lossless compression with Brotli in Rust (2016)](https://dropbox.tech/infrastructure/lossless-compression-with-brotli), [Wikipedia: Optimal Tip-to-Tip Efficiency (season 1 finale)](https://en.wikipedia.org/wiki/Optimal_Tip-to-Tip_Efficiency)" + } + ] +} diff --git a/content/quizzes/silicon-valley-became-real/not-hotdog-class-balance.svg b/content/quizzes/silicon-valley-became-real/not-hotdog-class-balance.svg new file mode 100644 index 0000000..4570edf --- /dev/null +++ b/content/quizzes/silicon-valley-became-real/not-hotdog-class-balance.svg @@ -0,0 +1,58 @@ + + + + + + 150,000 training photos: 49 not-hotdogs for every hotdog + + + + + + + Not hotdog: 147,000 (98%) + + Hotdog: 3,000 (2%) + + A model that always answers "not hotdog": + right on all 147,000 not-hotdogs, wrong on all 3,000 hotdogs + 98% accuracy, and it has never found a hotdog. + + diff --git a/content/quizzes/silicon-valley-became-real/not-hotdog-pipeline.svg b/content/quizzes/silicon-valley-became-real/not-hotdog-pipeline.svg new file mode 100644 index 0000000..4800de0 --- /dev/null +++ b/content/quizzes/silicon-valley-became-real/not-hotdog-pipeline.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + Train on a laptop, then run on the phone + + + + + Training, on a laptop with an external GPU + + + Pretrained network + already knows edges, + textures, shapes + + + + + Retrain on food photos + 3,000 hotdogs, + 147,000 not hotdogs + + + + + Small model file + MobileNets-style, + about 4M parameters + + + ships inside the app + + + On the phone, no server involved + + + Camera photo + never uploaded + + + + + Neural network + runs on the phone + + + + + Hotdog + or + Not hotdog + + Faster (no round trip), works offline, photos stay private, and no server bill per photo. + + diff --git a/content/quizzes/silicon-valley-became-real/weissman-formula.svg b/content/quizzes/silicon-valley-became-real/weissman-formula.svg new file mode 100644 index 0000000..768f82d --- /dev/null +++ b/content/quizzes/silicon-valley-became-real/weissman-formula.svg @@ -0,0 +1,76 @@ + + + + + + The Weissman score, term by term + + + + + W + = + α + · + + r + + r̄ + + · + + log T̄ + + log T + + + + scaling constant + + Ratio term + r = your compression ratio + r̄ = a standard compressor's + + Time term + T = your time to compress + T̄ = the standard's time + + Higher W is better. A bigger ratio or a shorter time raises it. + + diff --git a/content/quizzes/tech-mascots-origin-stories.json b/content/quizzes/tech-mascots-origin-stories.json new file mode 100644 index 0000000..7b81855 --- /dev/null +++ b/content/quizzes/tech-mascots-origin-stories.json @@ -0,0 +1,442 @@ +{ + "schemaVersion": 1, + "id": "tech-mascots-origin-stories", + "title": "Tech mascots: where the penguin, the Octocat and the gopher came from", + "description": "Linux has a penguin, GitHub has a cat with octopus arms, Go has a gopher, and Firefox is named after an animal that isn't in its logo. Each Question asks where a mascot came from, what it was first called, or why the joke stuck. The Explanations also cover licensing, because whoever owns a mascot decides who may draw it. For anyone who has seen these characters on stickers and wondered about them.", + "language": "en", + "tags": [ + "open-source", + "design", + "internet-culture" + ], + "questions": [ + { + "id": "tux-penguin-bite", + "title": "Linus Torvalds says a penguin once bit him. Where did it happen?", + "description": "He tells the story as the reason he became fond of penguins, before Linux had any mascot at all.", + "type": "single-choice", + "options": [ + { + "text": "At the National Zoo & Aquarium in Canberra, Australia, where a little penguin nibbled him", + "isCorrect": true + }, + { + "text": "At Helsinki Zoo, near the university where he wrote the first Linux kernel", + "isCorrect": false + }, + { + "text": "On a beach on Phillip Island, where he tried to photograph wild penguins", + "isCorrect": false + }, + { + "text": "At a research station in Antarctica, during a trip sponsored by a Linux company", + "isCorrect": false + } + ], + "explanation": "Around 1993, Torvalds visited Australia to talk about Linux to the Australian Unix Users Group. At the zoo in Canberra, a little penguin nibbled him. Australians call the species the fairy penguin, and he later joked that the \"ferocious fairy penguin\" should be kept locked up.\n\nPhillip Island is a reasonable guess. It is famous for its wild little penguins, and some retellings move the story there. Torvalds himself wrote in *Linux Journal* that it happened at the Canberra zoo.\n\nThe bite alone didn't make Tux the mascot. In May 1996, when people asked why Linux should have a penguin, he answered: \"Linus likes penguins. That's it.\" The neater versions of the story came later.", + "references": "[Wikipedia: Tux (mascot)](https://en.wikipedia.org/wiki/Tux_%28mascot%29), [Wikipedia: Little penguin](https://en.wikipedia.org/wiki/Little_penguin)" + }, + { + "id": "tux-linus-brief", + "title": "In May 1996, Linus Torvalds told the kernel mailing list what the Linux penguin should look like. What did he ask for?", + "description": "He was replying to a prototype logo that showed a penguin holding up the globe.", + "type": "single-choice", + "options": [ + { + "text": "A slightly overweight, contented penguin sitting down, too stuffed with herring to stand up", + "isCorrect": true + }, + { + "text": "A strong penguin holding up the world like Atlas, to show that Linux could carry serious workloads", + "isCorrect": false + }, + { + "text": "A fierce penguin chasing the Windows logo, to show Linux was the challenger", + "isCorrect": false + }, + { + "text": "A plain geometric penguin shape, simple enough to print in one colour on a floppy disk label", + "isCorrect": false + } + ], + "explanation": "Torvalds rejected the globe prototype because the penguin didn't look strong enough to hold up the world. It looked about to be squashed, which is a poor image for a logo.\n\nHe asked people to think \"cuddly\", \"cute\" and \"contented\". For a penguin, he explained, contented means stuffed with herring: it sits down because it has eaten too much to stand, and smiles as if it has just burped. He also said he didn't want a \"macho penguin\".\n\nA few days later he explained why he preferred a character to an abstract mark like the Windows logo. You can redraw a character doing something new, such as a penguin typing on a word processor, and people still recognise it. You can't do that with an abstract logo, which is why there are so many versions of Tux.", + "references": "[Linus Torvalds, \"Re: Linux Logo prototype\", linux-kernel, 9 May 1996 (archived by Steve Baker)](https://web.archive.org/web/2015/http://www.sjbaker.org/wiki/index.php?title=The_History_of_Tux_the_Linux_Penguin)" + }, + { + "id": "tux-drawn-in-gimp", + "title": "Larry Ewing drew the penguin that became Tux. Which program did he use?", + "type": "single-choice", + "options": [ + { + "text": "GIMP 0.54, the first public release of the free image editor", + "isCorrect": true + }, + { + "text": "Adobe Photoshop 3.0, the standard tool for digital artists in 1996", + "isCorrect": false + }, + { + "text": "Xfig, the vector drawing program that shipped with most Unix systems", + "isCorrect": false + }, + { + "text": "Microsoft Paint, as a joke about Linux's rival", + "isCorrect": false + } + ], + "explanation": "Ewing painted Tux in 1996 in GIMP 0.54, the first public release of the GNU Image Manipulation Program. A free-software mascot drawn with a free-software tool suited Linux well. Ewing also wrote up how he made it, as a guide to drawing in GIMP.\n\nHe released the image with one condition: you may use and modify it if you credit him and The GIMP \"if someone asks\". That is why the caption below names both.\n\nTux never won a logo contest. There were three, and Tux won none of them. Officially, Tux is the Linux *brand character* rather than its logo.", + "references": "[Larry Ewing: Linux 2.0 Penguins (archived)](https://web.archive.org/web/2020/https://isc.tamu.edu/~lewing/linux/), [Wikipedia: Tux (mascot)](https://en.wikipedia.org/wiki/Tux_%28mascot%29), [Wikipedia: GIMP](https://en.wikipedia.org/wiki/GIMP)", + "images": [ + { + "src": "tux-larry-ewing-1996.png", + "alt": "The original Tux: a plump black-and-white penguin with a yellow beak and yellow feet, sitting down with a contented look.", + "caption": "Larry Ewing's original 1996 Tux, placed on a white background. By Larry Ewing (lewing@isc.tamu.edu) and The GIMP, used under his use-with-credit permission, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Tux.png).", + "placement": "explanation", + "width": 305, + "height": 354 + } + ] + }, + { + "id": "tux-name-acronym", + "title": "The first suggestion to call the penguin \"Tux\" explained the name as an acronym. The T stood for Torvalds. What did UX stand for?", + "description": "Type one word.", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": [ + "Unix", + "Uni X" + ] + }, + "explanation": "On 10 June 1996, the day after Linux 2.0 came out, someone on the kernel mailing list proposed a contest to name the penguin. James Hughes replied with \"(T)orvalds (U)ni(X) --> TUX!\"\n\nThe acronym is a stretch, since the U and the X both come from one word. The name stuck for another reason: a penguin's black-and-white feathers look like a tuxedo, and \"tux\" is short for tuxedo.", + "references": "[James Hughes, \"Re: Let's name the penguin!\", linux-kernel, 10 June 1996 (archived by Steve Baker)](https://web.archive.org/web/2015/http://www.sjbaker.org/wiki/index.php?title=The_History_of_Tux_the_Linux_Penguin), [Wikipedia: Tux (mascot)](https://en.wikipedia.org/wiki/Tux_%28mascot%29)" + }, + { + "id": "octocat-original-name", + "title": "Before GitHub renamed it, what was the Octocat called?", + "type": "single-choice", + "options": [ + { + "text": "Octopuss", + "isCorrect": true + }, + { + "text": "Mona", + "isCorrect": false + }, + { + "text": "Inkitty", + "isCorrect": false + }, + { + "text": "Gitcat", + "isCorrect": false + } + ], + "explanation": "British illustrator Simon Oxley drew a character with a cat's head and octopus arms and sold it as stock clip art on iStock, under the name \"Octopuss\". GitHub's founders bought it for the site's error pages and renamed it the Octocat.\n\nMona is a real name too, but it came later. The daughter of a GitHub developer wrote a story about a cat called Monalisa who swallowed some coral and grew octopus legs. When GitHub wrote down the character's personality, it adopted the name Mona Lisa, or Mona for short.\n\nThat character guide has a rule you may not have noticed: the Octocat never speaks. Cameron McEfee, who wrote the guide, had watched talking mascots such as Clippy become annoying. The Octocat shows how it feels through its face and what it does.", + "references": "[Cameron McEfee: The Octocat](https://cameronmcefee.com/work/the-octocat/), [Wikipedia: GitHub, Mascot](https://en.wikipedia.org/wiki/GitHub#Mascot). Related Quiz: *The internet runs on cats*." + }, + { + "id": "octocat-octopus-merge", + "title": "Why did a half-cat, half-octopus fit GitHub so well?", + "type": "single-choice", + "options": [ + { + "text": "Git has an \"octopus merge\", which combines three or more branches in one commit", + "isCorrect": true + }, + { + "text": "Git's short commit hashes are eight characters long by default, one for each arm", + "isCorrect": false + }, + { + "text": "GitHub was founded by eight people, one for each arm", + "isCorrect": false + }, + { + "text": "Git stores file permissions as octal numbers", + "isCorrect": false + } + ], + "explanation": "An ordinary merge commit has two parents. An octopus merge has more. If you name several branches in one command, Git uses the octopus strategy and records a single commit with one parent per branch:\n\n```bash\ngit merge feature-a feature-b feature-c\n```\n\nThe octopus strategy refuses any merge that needs manual conflict resolution. It is for bundling branches that don't conflict.\n\nThe other options are wrong. Git's short hashes are at least 7 characters by default and grow when needed to stay unique. GitHub had four founders. Git does record file modes such as `100644` in octal, but the mascot has nothing to do with them. And the Octocat has five arms, not eight.", + "references": "[Git documentation: git-merge, merge strategies](https://git-scm.com/docs/git-merge#_merge_strategies), [Cameron McEfee: The Octocat](https://cameronmcefee.com/work/the-octocat/)" + }, + { + "id": "oxley-other-logo", + "title": "Simon Oxley, who drew the Octopuss, sold another stock illustration that became a famous tech logo. Which one?", + "type": "single-choice", + "options": [ + { + "text": "The original Twitter bird", + "isCorrect": true + }, + { + "text": "The Android robot", + "isCorrect": false + }, + { + "text": "The Firefox fox", + "isCorrect": false + }, + { + "text": "The Chrome offline dinosaur", + "isCorrect": false + } + ], + "explanation": "Twitter's first bird logo was a cheap stock image by Oxley, bought from iStock. The Android robot, the Firefox fox and the Chrome dinosaur were all drawn for Google or Mozilla.\n\nThe licence explains why the two purchases ended differently. A standard stock licence lets many customers use the same image, so it doesn't allow a buyer to use it as a logo or trademark. GitHub wanted the Octopuss as its logo, so it negotiated with Oxley for exclusive rights, renamed the character and registered the Octocat as a trademark. Twitter had its bird redrawn instead, and later versions were new designs that Twitter owned.", + "references": "[Wikipedia: Twitter, Branding](https://en.wikipedia.org/wiki/Twitter#Branding), [Wikipedia: GitHub, Mascot](https://en.wikipedia.org/wiki/GitHub#Mascot)" + }, + { + "id": "firefox-red-panda", + "title": "\"Firefox\" is a common name for which animal?", + "type": "single-choice", + "options": [ + { + "text": "The red panda", + "isCorrect": true + }, + { + "text": "The red fox", + "isCorrect": false + }, + { + "text": "The fennec fox", + "isCorrect": false + }, + { + "text": "The Arctic fox", + "isCorrect": false + } + ], + "explanation": "Mozilla said the browser's name came from a nickname for the red panda. The logo still shows a fox wrapped around a globe. Jon Hicks, who drew the first logo in 2004, said a red panda \"didn't really conjure up the right imagery\", and few people knew the animal anyway.\n\nThe fennec fox is a fair guess, because Mozilla did use it: \"Fennec\" was the code name for the old mobile version of Firefox.", + "references": "[Wikipedia: Firefox, Branding and visual identity](https://en.wikipedia.org/wiki/Firefox#Branding_and_visual_identity), [Wikipedia: Firefox logo](https://en.wikipedia.org/wiki/Firefox_logo)" + }, + { + "id": "firefox-earlier-names", + "title": "Which names did the browser use before it became Mozilla Firefox in February 2004?", + "type": "multiple-choice", + "options": [ + { + "text": "Phoenix", + "isCorrect": true + }, + { + "text": "Mozilla Firebird", + "isCorrect": true + }, + { + "text": "Mozilla Thunderbird", + "isCorrect": false + }, + { + "text": "Camino", + "isCorrect": false + } + ], + "explanation": "The browser started in 2002 as **Phoenix**, the bird that rises from the ashes, a nod to Netscape's defeat by Internet Explorer. Phoenix Technologies, a maker of PC BIOS software, claimed the trademark, so in 2003 the browser became **Firebird**.\n\nThat name clashed with the Firebird database, an open source project that complained loudly. Mozilla first promised to always write \"Mozilla Firebird\" to avoid confusion. After more pressure, it switched to Firefox on 9 February 2004.\n\nThe other two names are real but belong to other products. Thunderbird is Mozilla's email client, a sibling of the browser. Camino was a separate Mac browser built on Mozilla's engine.", + "references": "[Wikipedia: Firefox, Name changes](https://en.wikipedia.org/wiki/Firefox#Name_changes), [Wikipedia: Firefox logo](https://en.wikipedia.org/wiki/Firefox_logo)" + }, + { + "id": "chrome-dino-codename", + "title": "What was the Chrome offline dinosaur game's code name during development?", + "type": "single-choice", + "options": [ + { + "text": "Project Bolan", + "isCorrect": true + }, + { + "text": "Project Jurassic", + "isCorrect": false + }, + { + "text": "Project Meteor", + "isCorrect": false + }, + { + "text": "Project Sue", + "isCorrect": false + } + ], + "explanation": "Sebastien Gabriel, Alan Bettes and Edward Jung of the Chrome UX team built the game in 2014. They called it Project Bolan, after Marc Bolan, the singer of T. Rex, so the code name is a dinosaur pun. Gabriel called the character the \"Lonely T-Rex\".\n\nThe game's world does have a meteor. If a network administrator disables the game, the error page shows a meteor heading for the dinosaur.", + "references": "[Wikipedia: Dinosaur Game](https://en.wikipedia.org/wiki/Dinosaur_Game)" + }, + { + "id": "chrome-dino-facts", + "title": "Which statements about the Chrome offline dinosaur are true?", + "type": "multiple-choice", + "options": [ + { + "text": "The dinosaur is a joke that having no internet connection is like living in prehistoric times", + "isCorrect": true + }, + { + "text": "You can play it while online by typing `chrome://dino` in the address bar", + "isCorrect": true + }, + { + "text": "Flying pterosaurs were added as obstacles in a later update", + "isCorrect": true + }, + { + "text": "It appears when Google's servers are down, even if your own connection works", + "isCorrect": false + }, + { + "text": "Its pixel-art sprite was traced from the famous T. rex skeleton \"Sue\" in Chicago", + "isCorrect": false + } + ], + "explanation": "The team picked a dinosaur as a joke: without an internet connection, you're back in prehistoric times. The game came out in September 2014, and pterosaurs arrived as a second kind of obstacle in 2015.\n\nThe page appears when *your* browser can't reach the network, for example with Wi-Fi off. If Google's servers were down, you would see a different error, from a site that fails to respond.\n\nThe design team drew the sprite themselves. It has nothing to do with Sue or any other fossil.\n\nTry it now: open `chrome://dino` in Chrome and press Space to jump.", + "references": "[Wikipedia: Dinosaur Game](https://en.wikipedia.org/wiki/Dinosaur_Game)" + }, + { + "id": "go-gopher-first-appearance", + "title": "Where did the Go gopher first appear, years before the Go language existed?", + "type": "single-choice", + "options": [ + { + "text": "On a T-shirt for a fundraiser at WFMU, a radio station in New Jersey", + "isCorrect": true + }, + { + "text": "As a user's avatar in the Bell Labs email system", + "isCorrect": false + }, + { + "text": "As the mascot of Plan 9, the operating system Go's creators worked on", + "isCorrect": false + }, + { + "text": "In the logo of Google Code, Google's old project-hosting site", + "isCorrect": false + } + ], + "explanation": "Around 1999, long before Go, the radio station WFMU asked illustrator Renée French to design a T-shirt for its annual fundraiser, and she drew a gopher. When Go launched as open source in 2009, French suggested adapting that gopher as the mascot.\n\nTwo of the wrong answers are close. The gopher's *second* appearance was as Bob Flandrena's avatar in the Bell Labs mail system. Plan 9's mascot is Glenda, a rabbit French also drew, which the Go blog calls a distant cousin of the gopher.\n\nThe gopher has no name. It is just the Go gopher.", + "references": "[The Go Blog: The Go Gopher (Rob Pike and Andrew Gerrand, 2014)](https://go.dev/blog/gopher)", + "images": [ + { + "src": "go-gopher-renee-french.png", + "alt": "The Go gopher, drawn in black outline: a tall, round-bodied rodent with small ears, large round eyes, two big front teeth and stubby arms and feet.", + "caption": "The Go gopher, by Renée French. CC BY 4.0, from [go.dev](https://go.dev/doc/gopher/).", + "placement": "explanation", + "width": 250, + "height": 340 + } + ] + }, + { + "id": "public-domain-mascot", + "title": "Which of these mascots has its creator dedicated to the public domain?", + "type": "single-choice", + "options": [ + { + "text": "Ferris, Rust's unofficial crab mascot", + "isCorrect": true + }, + { + "text": "The Go gopher", + "isCorrect": false + }, + { + "text": "Duke, the Java mascot", + "isCorrect": false + }, + { + "text": "Tux, the Linux penguin", + "isCorrect": false + } + ], + "explanation": "Karen Rustad Tölva, who drew Ferris, released it under CC0. CC0 waives all copyright \"to the extent possible under law\", so you can copy, change and sell Ferris art without asking or giving credit.\n\nThe other three are free to use, with conditions:\n\n- **Go gopher:** CC BY 4.0. You may do almost anything with it if you credit Renée French.\n- **Duke:** the BSD licence, from Sun Microsystems. It allows commercial use and changes.\n- **Tux:** Larry Ewing's own permission. You may use and modify it if you credit him and The GIMP when someone asks.\n\nAll four licences cover copyright only. CC0 says it does not waive trademark rights, and the others don't grant them. Copyright decides who may copy a drawing. Trademark decides who may use a name or mark to identify a product.", + "references": "[Rustacean.net: Home of Ferris the Crab](https://rustacean.net/), [Creative Commons: CC0 1.0 legal code](https://creativecommons.org/publicdomain/zero/1.0/legalcode.en), [Go brand guidelines](https://go.dev/brand), [Wikipedia: Tux (mascot)](https://en.wikipedia.org/wiki/Tux_%28mascot%29)", + "images": [ + { + "src": "ferris-the-crab.svg", + "alt": "Ferris: a wide orange cartoon crab with a spiky-edged shell, two big round eyes, a small smile and pincers on either side.", + "caption": "Ferris the crab, by Karen Rustad Tölva. Public domain (CC0), from [rustacean.net](https://rustacean.net/). White background added.", + "placement": "explanation", + "width": 600, + "height": 400 + }, + { + "src": "duke-waving.svg", + "alt": "Duke: a tall black-and-white figure shaped like a rounded triangle, with a large shiny red nose, waving one black hand.", + "caption": "Duke waving. Sun Microsystems, BSD licence, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Duke_%28Java_mascot%29_waving.svg). White background added.", + "placement": "explanation", + "width": 266, + "height": 447 + } + ] + }, + { + "id": "go-gopher-versus-go-logo", + "title": "Go's brand guidelines treat the gopher differently from the word \"Go\" and the Go logo. What is the difference?", + "type": "single-choice", + "options": [ + { + "text": "The gopher is a drawing under a copyright licence, CC BY 4.0. The word and the logo are Google trademarks with usage rules", + "isCorrect": true + }, + { + "text": "The gopher is a Google trademark, while the word \"Go\" and the logo are free for anyone to use", + "isCorrect": false + }, + { + "text": "All three share the BSD licence that covers the Go source code", + "isCorrect": false + }, + { + "text": "The gopher may be used only by official Go user groups and conferences", + "isCorrect": false + } + ], + "explanation": "The guidelines say the word mark \"Go\" and the Go logo are Google trademarks, separate from the BSD licence on the source code. The gopher, by Renée French, is under the Creative Commons Attribution 4.0 licence.\n\nThese are two kinds of rights:\n\n- **Copyright** protects a specific drawing. CC BY lets you copy and change the gopher, even to make a mascot for your user group, as long as you credit French.\n- **Trademark** protects a name or mark that identifies a product. Anyone may use the word \"Go\" to talk about the language. Nobody may use it to name a different language or to suggest that Google endorses their product.\n\nOne project can have both. A free licence to draw the gopher gives you no right to use the Go trademark.", + "references": "[Go brand and trademark usage guidelines](https://go.dev/brand), [The Go Blog: The Go Gopher](https://go.dev/blog/gopher)" + }, + { + "id": "duke-open-sourced-year", + "title": "In what year did Sun Microsystems release the Duke images under the BSD licence?", + "description": "Enter a four-digit year. Answers within one year are accepted.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 2006 + ], + "tolerance": 1 + }, + "explanation": "On 13 November 2006, Sun announced that Duke would become \"Free Graphics\". It was the same announcement in which Sun said its Java SE and Java ME implementations would become free software, so the mascot and the platform were freed together.\n\nDuke is older than Java. Joe Palrang created him as the \"agent\" character for the Green Project, the Sun research project that produced the Java language, and he became Java's mascot when Java was announced.\n\nSun's FAQ separated two kinds of rights. The BSD licence covered the *image files*. Sun claimed no rights in the *name* Duke and asked only that people not use him to disparage Java.", + "references": "[Duke Images project home (Project Kenai, archived)](https://web.archive.org/web/20150604165108/https://kenai.com/projects/duke/pages/Home), [Wikimedia Commons: Duke (Java mascot) waving](https://commons.wikimedia.org/wiki/File:Duke_%28Java_mascot%29_waving.svg)" + }, + { + "id": "mascot-origins-boss", + "title": "Which mascots are matched with their true origin?", + "type": "multiple-choice", + "options": [ + { + "text": "Go gopher: a radio station fundraiser T-shirt", + "isCorrect": true + }, + { + "text": "Octocat: stock clip art bought for an error page", + "isCorrect": true + }, + { + "text": "Tux: a drawing made in the first public release of GIMP", + "isCorrect": true + }, + { + "text": "Chrome dinosaur: the agent character of Sun's Green Project", + "isCorrect": false + }, + { + "text": "Ferris: drawn by Renée French for the Go and Rust launches", + "isCorrect": false + } + ], + "explanation": "Renée French drew the Go gopher for WFMU years before Go existed. The Octocat was Simon Oxley's \"Octopuss\", stock clip art that GitHub put on its error pages before buying exclusive rights. Larry Ewing drew Tux in GIMP 0.54 and let anyone use it with credit.\n\nThe two false matches swap details between mascots. The Green Project agent was Duke, who became Java's mascot. The Chrome dinosaur came from Google's Chrome UX team in 2014. Ferris was drawn by Karen Rustad Tölva. Renée French drew the gopher and Plan 9's Glenda, but not Ferris.\n\nFew of these mascots were designed as logos. They started as clip art, a T-shirt or an error-page joke, and developers kept them because they liked them.", + "references": "[The Go Blog: The Go Gopher](https://go.dev/blog/gopher), [Cameron McEfee: The Octocat](https://cameronmcefee.com/work/the-octocat/), [Wikipedia: Tux (mascot)](https://en.wikipedia.org/wiki/Tux_%28mascot%29), [Rustacean.net](https://rustacean.net/), [Wikipedia: Dinosaur Game](https://en.wikipedia.org/wiki/Dinosaur_Game)" + } + ] +} diff --git a/content/quizzes/tech-mascots-origin-stories/duke-waving.svg b/content/quizzes/tech-mascots-origin-stories/duke-waving.svg new file mode 100644 index 0000000..ecdd75e --- /dev/null +++ b/content/quizzes/tech-mascots-origin-stories/duke-waving.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/quizzes/tech-mascots-origin-stories/ferris-the-crab.svg b/content/quizzes/tech-mascots-origin-stories/ferris-the-crab.svg new file mode 100644 index 0000000..7ede7fa --- /dev/null +++ b/content/quizzes/tech-mascots-origin-stories/ferris-the-crab.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/quizzes/tech-mascots-origin-stories/go-gopher-renee-french.png b/content/quizzes/tech-mascots-origin-stories/go-gopher-renee-french.png new file mode 100644 index 0000000..1eb81f0 Binary files /dev/null and b/content/quizzes/tech-mascots-origin-stories/go-gopher-renee-french.png differ diff --git a/content/quizzes/tech-mascots-origin-stories/tux-larry-ewing-1996.png b/content/quizzes/tech-mascots-origin-stories/tux-larry-ewing-1996.png new file mode 100644 index 0000000..378e4c1 Binary files /dev/null and b/content/quizzes/tech-mascots-origin-stories/tux-larry-ewing-1996.png differ diff --git a/content/quizzes/tech-myths-that-wont-die.json b/content/quizzes/tech-myths-that-wont-die.json new file mode 100644 index 0000000..cf5b1a3 --- /dev/null +++ b/content/quizzes/tech-myths-that-wont-die.json @@ -0,0 +1,538 @@ +{ + "schemaVersion": 1, + "id": "tech-myths-that-wont-die", + "title": "Tech myths that won't die: 404, 640K, the space pen and Nuclear Gandhi", + "description": "Room 404 at CERN, Bill Gates's 640K quote, NASA's million-dollar space pen, the Gandhi bug in *Civilization*, Grace Hopper finding the first bug. They're great stories, and most of them are wrong. Each Question starts from the popular version and asks what really happened. The Explanations show where each myth came from and why it caught on.", + "language": "en", + "tags": [ + "critical-thinking", + "internet-culture", + "everyday-life" + ], + "author": "Quizbun", + "questions": [ + { + "id": "room-404-at-cern", + "title": "Is the 404 error named after Room 404 at CERN, where the web's first servers supposedly sat?", + "type": "single-choice", + "options": [ + { + "text": "No. Web pioneer Robert Cailliau says 404 was never linked to any room at CERN", + "isCorrect": true + }, + { + "text": "Yes. Tim Berners-Lee's office was Room 404, and he was often out", + "isCorrect": false + }, + { + "text": "Yes, but the room was a server closet, not an office", + "isCorrect": false + }, + { + "text": "No. It's named after the fourth floor of the first web host in California", + "isCorrect": false + } + ], + "explanation": "There was no such room. The story showed up in the early 2000s: the first web servers sat in Room 404, Tim Berners-Lee worked there, and he was often \"not found\". It's a neat story, and it's wrong.\n\nIn 2017, Wired asked Robert Cailliau, who built the web with Berners-Lee at CERN. He answered: \"404 was never linked to any room or any physical place at CERN. That's a complete myth.\" He said the codes were grouped into ranges \"according to the whims of the programmer\". Client errors went in the 400s, and \"not found\" happened to get 404.\n\nThe myth spreads well because it has a famous name, a specific detail and a punchline. None of that is evidence. Ask where the room number comes from, and you'll find no source.", + "references": "[Wired: Page not found — a brief history of the 404 error (2017)](https://www.wired.com/story/page-not-found-a-brief-history-of-the-404-error/)" + }, + { + "id": "what-the-4-in-4xx-means", + "title": "In an HTTP status code such as 404, what does the first digit 4 tell you?", + "type": "single-choice", + "options": [ + { + "text": "The error is on the client's side: the request can't be fulfilled as sent", + "isCorrect": true + }, + { + "text": "The server crashed or failed while handling a valid request", + "isCorrect": false + }, + { + "text": "The resource has moved, and the client should look elsewhere", + "isCorrect": false + }, + { + "text": "The request was retried four times before failing", + "isCorrect": false + } + ], + "explanation": "An HTTP status code has three digits, and only the first one sets the category. RFC 9110 defines five classes:\n\n| First digit | Class | Meaning |\n| --- | --- | --- |\n| 1xx | Informational | Request received, continuing |\n| 2xx | Successful | Request received, understood and accepted |\n| 3xx | Redirection | Further action needed, such as following a new URL |\n| 4xx | Client Error | The client seems to have erred |\n| 5xx | Server Error | The server failed to perform a valid request |\n\nA 404 means the server found nothing at the URL the client asked for. The server works fine; the request points at something that isn't there. That's why a typo in a URL gets a 404, while a crashed database behind a correct URL should get a 500.\n\nThe RFC also says the last two digits \"do not have any categorization role\". So 404 isn't \"error type 04\" of anything. It's just the number \"not found\" got, which fits Cailliau's account of how the codes were picked.", + "references": "[RFC 9110: HTTP Semantics, §15 Status Codes](https://www.rfc-editor.org/rfc/rfc9110.html#name-status-codes), [Wired: Page not found — a brief history of the 404 error (2017)](https://www.wired.com/story/page-not-found-a-brief-history-of-the-404-error/)" + }, + { + "id": "did-gates-say-640k", + "title": "Did Bill Gates say that \"640K ought to be enough for anybody\"?", + "type": "single-choice", + "options": [ + { + "text": "There's no evidence he did, and he has denied it", + "isCorrect": true + }, + { + "text": "Yes, at the IBM PC launch in 1981", + "isCorrect": false + }, + { + "text": "Yes, in a 1981 interview with InfoWorld", + "isCorrect": false + }, + { + "text": "Yes, but he meant it as a joke about MS-DOS", + "isCorrect": false + } + ], + "explanation": "Nobody has found a recording, transcript or article from 1981 where Gates says it. The quote usually comes with no date, or it gets \"1981\" because that's when the IBM PC shipped.\n\nGates answered it in his newspaper column in 1996. He wrote that he had said \"some stupid things and some wrong things, but not that\", and complained that the quote never comes with a citation and just \"floats like a rumor\".\n\nA denial alone proves little, since people misremember what they said. The paper trail is stronger evidence. Quote Investigator, who searches old newspapers and magazines for the first appearance of famous quotes, found the line only years after 1981, and in a different form. The next Question asks when.", + "references": "[Quote Investigator: 640K of computer memory is more than enough for anyone (2011)](https://quoteinvestigator.com/2011/09/08/640k-enough/)" + }, + { + "id": "640k-quote-first-print-year", + "title": "In what year did a version of the 640K quote first appear in print?", + "description": "Answer with a four-digit year.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 1985 + ], + "tolerance": 1 + }, + "explanation": "**1985**, four years after the IBM PC launched. On 29 April 1985, InfoWorld printed a line credited to Gates about setting the PC-DOS limit at 640K and thinking \"nobody would ever need that much memory\". That's a different claim from the famous one. It says the designers underestimated demand. It doesn't say 640K would be enough forever.\n\nThe punchy version, \"640K ought to be enough for anyone\", appears in InfoWorld on 1 January 1990. By then it was a stock example in columns about bad tech predictions.\n\nMany misquotes grow this way. Someone paraphrases a real but vague remark, each retelling makes it sharper, and a date gets attached later so it sounds like a prediction. When a quote has no source from the year it was supposedly said, find its first appearance in print. The gap between the two years tells you a lot.", + "references": "[Quote Investigator: 640K of computer memory is more than enough for anyone (2011)](https://quoteinvestigator.com/2011/09/08/640k-enough/)", + "images": [ + { + "src": "640k-quote-paper-trail.svg", + "alt": "A timeline of the 640K quote. 1981: supposedly said when the IBM PC launched, but nobody has found a source from 1981. 1985: first in print, in InfoWorld on 29 April, as a line credited to Gates about setting the PC-DOS limit at 640K and thinking nobody would ever need that much memory. 1990: the famous wording, \"640K ought to be enough for anyone\", in InfoWorld on 1 January. 1996: Gates denies it in his newspaper column, saying the quote never has a citation and floats like a rumor.", + "caption": "Dates and publications from Quote Investigator. Timeline drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 440 + } + ] + }, + { + "id": "8088-address-space", + "title": "The IBM PC's Intel 8088 processor had 20 address lines, each carrying one bit of a memory address. How many kilobytes of memory could it address?", + "description": "1 KB = 1,024 bytes. Answer with a whole number of kilobytes.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 1024 + ] + }, + "explanation": "20 bits give 2²⁰ = 1,048,576 addresses, one byte each. Divide by 1,024 bytes per kilobyte and you get **1,024 KB**, or 1 MB.\n\nSo where does 640 KB come from? When IBM designed the PC in 1981, it had to fit more than RAM into that 1 MB of addresses. Video cards need memory the processor can write to, and the BIOS and expansion cards need ROM. IBM put all of these at the top of the address space, starting at address A0000 hex, which is 640 KB. That left 640 KB of continuous addresses for RAM, called **conventional memory**. The 384 KB above it is the **upper memory area**.\n\nDOS programs expected their memory in that one block, so adding RAM above 640 KB didn't help them. Later tricks such as expanded memory, extended memory and upper-memory loaders existed to work around this hardware layout. The limit had nothing to do with anyone's opinion of how much memory people needed.", + "references": "[Wikipedia: Intel 8088](https://en.wikipedia.org/wiki/Intel_8088), [Wikipedia: Conventional memory](https://en.wikipedia.org/wiki/Conventional_memory), [Wikipedia: Upper memory area](https://en.wikipedia.org/wiki/Upper_memory_area)", + "images": [ + { + "src": "ibm-pc-memory-map-640k.svg", + "alt": "A bar showing the IBM PC's 1,024 KB address space. The first 640 KB, from 0 to address A0000 hex, is RAM for DOS and programs, labeled conventional memory. The remaining 384 KB, the upper area, holds video memory from 640 to 768 KB, reserved space for adapter ROMs from 768 to 960 KB, and the BIOS from 960 KB up. A note says IBM put video memory and ROMs at the top in 1981, so extra RAM above 640 KB couldn't extend the block programs used.", + "caption": "Simplified from the IBM PC memory map. Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ] + }, + { + "id": "who-paid-for-the-space-pen", + "title": "The story goes that NASA spent millions on a pen that writes in space, while the Soviets just used pencils. Who actually paid to develop the Fisher Space Pen?", + "type": "single-choice", + "options": [ + { + "text": "Paul Fisher's pen company, with about $1 million of its own money", + "isCorrect": true + }, + { + "text": "NASA, through a multimillion-dollar development contract", + "isCorrect": false + }, + { + "text": "The U.S. Air Force, which later passed the design to NASA", + "isCorrect": false + }, + { + "text": "NASA and the Soviet space agency, as a joint project", + "isCorrect": false + } + ], + "explanation": "NASA didn't pay for it. Paul C. Fisher and his company spent about $1 million of their own money on a pressurized ink cartridge. Nitrogen gas pushes the thick ink toward the ball, so the pen writes without gravity, upside down, and across a wide range of temperatures.\n\nNASA tested the pen, then bought it. In 1968 it ordered 400 for the Apollo program at $2.39 each, a bulk discount on the $3.98 list price. In the photo, Walter Cunningham writes in orbit during Apollo 7, in October 1968.\n\nThe joke works because it confirms a common suspicion: big agencies waste money on problems a simple tool could solve. That suspicion had some basis, as the next Question shows. Here, though, the research was private and the pens were cheap.", + "references": "[Scientific American: Fact or fiction? NASA spent millions to develop a pen that would write in space (2006)](https://www.scientificamerican.com/article/fact-or-fiction-nasa-spen/), [Wikipedia: Space pen](https://en.wikipedia.org/wiki/Space_pen)", + "images": [ + { + "src": "apollo-7-cunningham-writing-with-space-pen.webp", + "alt": "Astronaut Walter Cunningham in a white flight suit inside the cramped Apollo 7 command module, head bent, writing on a notepad with a slim pen. A camera film magazine floats in the air near his hand.", + "caption": "Walter Cunningham writing with a space pen on day nine of Apollo 7, 1968. NASA photo, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Astro-space-pen.jpg).", + "placement": "explanation", + "width": 1280, + "height": 1280 + } + ] + }, + { + "id": "why-not-just-use-pencils", + "title": "\"Why not just use a pencil?\" Which of these were real reasons to keep pencils out of a spacecraft?", + "type": "multiple-choice", + "options": [ + { + "text": "Broken tips and graphite dust float around in weightlessness instead of falling to the floor", + "isCorrect": true + }, + { + "text": "Graphite conducts electricity, so floating fragments could get into electronics", + "isCorrect": true + }, + { + "text": "Wood and graphite burn, which became a serious worry after the 1967 Apollo 1 fire", + "isCorrect": true + }, + { + "text": "Pencils can't leave a mark without gravity pressing the lead onto the paper", + "isCorrect": false + }, + { + "text": "Pencil marks fade quickly in the cabin's radiation", + "isCorrect": false + } + ], + "explanation": "Pencils do write in orbit. Friction leaves the mark, and your hand supplies the pressure, not gravity. American and Soviet crews both used pencils early on.\n\nThe trouble was what pencils leave behind. On Earth, a snapped tip or graphite dust falls to the floor. In orbit it drifts and can end up in an astronaut's eye or in equipment. Graphite also conducts electricity, which you don't want near switches and circuit boards.\n\nThen there's fire. In January 1967, a fire in the Apollo 1 cabin killed Gus Grissom, Ed White and Roger Chaffee during a ground test in a pure-oxygen atmosphere. After that, NASA worked hard to remove flammable materials from spacecraft, and wood and graphite burn.\n\nMoney mattered too. In 1965 NASA paid $128.89 each for 34 mechanical pencils, and the public outcry pushed the agency to look for something cheaper. At $2.39, the pen was the cheap option.", + "references": "[Scientific American: Fact or fiction? NASA spent millions to develop a pen that would write in space (2006)](https://www.scientificamerican.com/article/fact-or-fiction-nasa-spen/), [Wikipedia: Apollo 1](https://en.wikipedia.org/wiki/Apollo_1)" + }, + { + "id": "soviet-cosmonauts-and-the-pen", + "title": "In the joke, Soviet cosmonauts just used pencils. What did they actually do?", + "type": "single-choice", + "options": [ + { + "text": "They used pencils at first, then bought Fisher space pens in 1969", + "isCorrect": true + }, + { + "text": "They used pencils throughout the Soviet space program", + "isCorrect": false + }, + { + "text": "They developed their own pressurized pen before the Americans", + "isCorrect": false + }, + { + "text": "They used grease pencils only, because graphite was banned", + "isCorrect": false + } + ], + "explanation": "Cosmonauts started with pencils, just like astronauts. In 1969, a year after NASA's order, the Soviet Union ordered 100 Fisher pens and 1,000 ink cartridges for its Soyuz missions, at the same bulk discount NASA got.\n\nThat kills the joke. The punchline needs the Soviets to pick the simple fix the Americans missed. In fact, both programs started with the \"simple\" fix, ran into the same problems, and bought the same pen.\n\nStories like this survive because they're easy to retell: a clever underdog, a wasteful giant and a one-line moral. The purchase records take them apart.", + "references": "[Scientific American: Fact or fiction? NASA spent millions to develop a pen that would write in space (2006)](https://www.scientificamerican.com/article/fact-or-fiction-nasa-spen/)" + }, + { + "id": "unsigned-8-bit-one-minus-two", + "title": "If you store 1 in an unsigned 8-bit integer and subtract 2, what value do you get?", + "description": "A famous *Civilization* legend depends on this calculation. Answer with a whole number.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 255 + ] + }, + "explanation": "An unsigned 8-bit integer holds 2⁸ = 256 values, from 0 to 255. It has no −1, so going below 0 **wraps around** to the top, like an odometer rolling backward. 1 − 1 = 0, and one more step back lands on 255. This is **integer underflow**, and many languages and processors do it for unsigned types.\n\nA **signed** 8-bit integer reads the same 8 bits differently and covers −128 to 127. The bit pattern for 255 unsigned means −1 signed. So in a signed variable, 1 − 2 = −1, as you'd expect.\n\nThis is the math behind the \"Nuclear Gandhi\" story. The legend says Gandhi's aggression in the first *Civilization* was 1, adopting democracy subtracted 2, and the value wrapped to 255. The most peaceful leader became the most aggressive one. The math checks out. The next Questions check the story.", + "references": "[Wikipedia: Integer overflow](https://en.wikipedia.org/wiki/Integer_overflow), [Wikipedia: Nuclear Gandhi](https://en.wikipedia.org/wiki/Nuclear_Gandhi)", + "images": [ + { + "src": "unsigned-8-bit-underflow-wheel.svg", + "alt": "The 256 values of an unsigned 8-bit integer drawn as a ring, with 0 at the top, 1 and 2 clockwise from it, 64 on the right, 128 at the bottom, 192 on the left, and 254 and 255 just counterclockwise of 0. An arrow goes two steps back from 1, past 0, to 255. A panel says unsigned 8-bit covers 0 to 255, so 1 − 2 = 255. A second panel says signed 8-bit covers −128 to 127 and reads the same bits as −1, so 1 − 2 = −1.", + "caption": "Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 440 + } + ] + }, + { + "id": "nuclear-gandhi-real-bug", + "title": "Was \"Nuclear Gandhi\" a real bug in the original 1991 *Civilization*?", + "type": "single-choice", + "options": [ + { + "text": "No. Sid Meier and Civilization II's lead designer, Brian Reynolds, both say it never happened", + "isCorrect": true + }, + { + "text": "Yes. Sid Meier confirmed it and kept it in later games as a tribute", + "isCorrect": false + }, + { + "text": "Yes, but a patch fixed it before most players saw it", + "isCorrect": false + }, + { + "text": "Nobody knows, because the original source code was lost", + "isCorrect": false + } + ], + "explanation": "The people who made the games say no. Brian Reynolds, lead designer of *Civilization II*, said he was \"99.99%\" sure the bug was apocryphal. Sid Meier, who created the series, took the story apart in his 2020 book, *Sid Meier's Memoir!*. The next Question covers their reasons.\n\nThe story does have a real ending, which helps keep it alive. In *Civilization V* (2010), designer Jon Shafer gave Gandhi the maximum rating for building and using nuclear weapons as a deliberate joke. Players who met a nuke-happy Gandhi took it as a nod to an old bug. It was really a nod to a myth.\n\nWhen a later game jokes about a legend, that shows the legend was popular. It doesn't show it was true.", + "references": "[Wikipedia: Nuclear Gandhi](https://en.wikipedia.org/wiki/Nuclear_Gandhi)" + }, + { + "id": "why-gandhi-underflow-couldnt-happen", + "title": "Which of these reasons from the game's own designers rule out the Nuclear Gandhi underflow in the original *Civilization*?", + "type": "multiple-choice", + "options": [ + { + "text": "Integer variables in the game's C code were signed by default, so 1 − 2 gives −1, not 255", + "isCorrect": true + }, + { + "text": "Switching to democracy didn't change an AI leader's aggression at all", + "isCorrect": true + }, + { + "text": "The game had only three aggression levels, and Gandhi shared the lowest with about a third of the leaders", + "isCorrect": true + }, + { + "text": "India wasn't a playable civilization in the original game", + "isCorrect": false + }, + { + "text": "The original game had no nuclear weapons", + "isCorrect": false + } + ], + "explanation": "Each of these three reasons rules out the story on its own.\n\n- **The arithmetic.** In C, `int` variables are signed unless you declare them otherwise. Meier pointed out in his memoir that 1 − 2 in a signed variable is −1. Wrapping to 255 needs an unsigned type, and the game didn't use one.\n- **The cause.** Meier also wrote that the form of government didn't affect AI aggression. Democracy never subtracted 2 from anything, so nothing could underflow.\n- **The scale.** Reynolds said there were only three aggression levels. Roughly a third of the leaders shared Gandhi's lowest level, so it wasn't special. No scale went up to 255 for Gandhi to wrap onto.\n\nThe other two Options are false: the 1991 game had India, led by Gandhi, and it had nuclear weapons.\n\nThe myth lasted because it *sounds* like real programming. Underflow is a real bug that has broken real software. That doesn't mean it happened in this game.", + "references": "[Wikipedia: Nuclear Gandhi](https://en.wikipedia.org/wiki/Nuclear_Gandhi), [Wikipedia: Civilization (video game)](https://en.wikipedia.org/wiki/Civilization_(video_game))" + }, + { + "id": "where-nuclear-gandhi-started", + "title": "Where is the earliest known version of the Nuclear Gandhi story?", + "type": "single-choice", + "options": [ + { + "text": "An unsourced 2012 edit to a TV Tropes page, about 21 years after the game came out", + "isCorrect": true + }, + { + "text": "A list of known bugs in the original 1991 game manual", + "isCorrect": false + }, + { + "text": "A 1990s magazine interview with Sid Meier", + "isCorrect": false + }, + { + "text": "The patch notes for Civilization II", + "isCorrect": false + } + ], + "explanation": "The earliest known version is a TV Tropes edit from 17 July 2012 that added the claim with no source. By then the first *Civilization* was over 20 years old.\n\nThe story took off in 2014, after a Reddit discussion about a reposted webcomic. A Kotaku article about Gandhi's aggression, published on 3 February 2014, was republished widely. From there it moved into listicles, trivia books and conference talks as an example of integer underflow.\n\nLook at that timeline. Nobody wrote about a bug in a 1991 game for two decades, and then everybody did. That points to a story that started in 2012. When you hear about a \"famous bug\", look for a source from when the software was current.", + "references": "[Wikipedia: Nuclear Gandhi](https://en.wikipedia.org/wiki/Nuclear_Gandhi)" + }, + { + "id": "moth-in-the-mark-ii-year", + "title": "In what year did operators find a moth in the Harvard Mark II computer and tape it into the logbook?", + "description": "Answer with a four-digit year.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 1947 + ], + "tolerance": 1 + }, + "explanation": "**1947**, on 9 September. The moth was stuck between the contacts of relay #70 on panel F of the Mark II, an electromechanical computer at Harvard. At 15:45 the operators removed it, taped it into the log and wrote \"First actual case of bug being found.\"\n\nThe Mark II was built from relays: mechanical switches that clicked open and shut thousands of times. An insect caught between a relay's contacts could stop it from closing. That's a hardware fault you can see with your own eyes. Software as we know it barely existed yet.", + "references": "[Naval History and Heritage Command: NH 96566-KN, The first \"computer bug\"](https://www.history.navy.mil/our-collections/photography/numerical-list-of-images/nhhc-series/nh-series/NH-96000/NH-96566-KN.html)" + }, + { + "id": "what-the-moth-log-page-shows", + "title": "This logbook page has a real moth taped to it. What does it show?", + "type": "single-choice", + "options": [ + { + "text": "A joke: engineers already called faults \"bugs\", and this time the bug was a real insect", + "isCorrect": true + }, + { + "text": "The moment the word \"bug\" was invented for technical faults", + "isCorrect": false + }, + { + "text": "The first software bug, found in a program's code", + "isCorrect": false + }, + { + "text": "Grace Hopper's personal notebook from the day she found the moth", + "isCorrect": false + } + ], + "explanation": "Look at the note the operators wrote: \"First **actual** case of bug being found.\" The word \"actual\" is the joke. Engineers already called faults \"bugs\", and this time the fault was a real insect.\n\nThe technical meaning is at least 70 years older. In an 1878 letter, Thomas Edison described how, after an invention's first flash of success, \"'Bugs'—as such little faults and difficulties are called—show themselves\". Webster's dictionary listed \"bug\" as a defect in an apparatus in 1934, well before this page.\n\nThe page doesn't show a software bug either. The moth jammed a relay, which is a mechanical switch. And it wasn't Grace Hopper's notebook, as the next Question explains.\n\nEven official captions sometimes repeat the myth. The Navy's own photo record says the operators' remark introduced the term \"debugging\". The page itself supports a smaller claim: a moth in a relay and an engineer's pun.", + "references": "[Naval History and Heritage Command: NH 96566-KN, The first \"computer bug\"](https://www.history.navy.mil/our-collections/photography/numerical-list-of-images/nhhc-series/nh-series/NH-96000/NH-96566-KN.html), [JSTOR Daily: The bug in the computer bug story (2017)](https://daily.jstor.org/the-bug-in-the-computer-bug-story/), [Wikipedia: Software bug, history](https://en.wikipedia.org/wiki/Software_bug)", + "images": [ + { + "src": "mark-ii-log-page-with-moth-1947.webp", + "alt": "A page of handwritten entries on graph paper, dated 9/9 at the top, with times down the left margin. Entries mention relays failing a speed test and a cosine tape starting. At 1545 a moth is stuck to the page with a strip of tape, next to the note \"Relay #70 Panel F (moth) in relay\". Below it is written \"First actual case of bug being found.\"", + "caption": "U.S. Naval History and Heritage Command photo NH 96566-KN, public domain, via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:First_Computer_Bug,_1947.jpg).", + "width": 1920, + "height": 1519 + } + ] + }, + { + "id": "was-it-hoppers-logbook", + "title": "The moth is often called \"Grace Hopper's bug\". What was her connection to it?", + "type": "single-choice", + "options": [ + { + "text": "She worked on the Mark II and told the story often, but her colleagues found the moth, and the log probably wasn't hers", + "isCorrect": true + }, + { + "text": "She found the moth herself and wrote the famous note", + "isCorrect": false + }, + { + "text": "She had no connection to the Mark II and only heard about it years later", + "isCorrect": false + }, + { + "text": "She kept the log as her private notebook and donated it to a museum", + "isCorrect": false + } + ], + "explanation": "Grace Hopper really was part of the story. She worked on the Harvard Mark II and Mark III, and in later decades she told the moth story in many talks. That's why her name stuck to it.\n\nThe log tells a smaller story. Accounts of the day say her colleagues found the moth, and the Smithsonian, which holds the page, says the log book \"was probably not Hopper's\". The operators wrote the joke.\n\nThe person who tells a story best often becomes its hero. Hopper did major work of her own, including early compilers and work that led to COBOL. Crediting her with a moth she didn't find adds nothing to her record and takes credit away from the operators.", + "references": "[Smithsonian National Museum of American History: Log book with computer bug](https://americanhistory.si.edu/collections/object/nmah_334663), [JSTOR Daily: The bug in the computer bug story (2017)](https://daily.jstor.org/the-bug-in-the-computer-bug-story/), [Wikipedia: Grace Hopper](https://en.wikipedia.org/wiki/Grace_Hopper)" + }, + { + "id": "where-the-moth-log-is-now", + "title": "Where is the logbook page with the moth today?", + "type": "single-choice", + "options": [ + { + "text": "In the Smithsonian's National Museum of American History", + "isCorrect": true + }, + { + "text": "In a display case at Harvard's Science Center", + "isCorrect": false + }, + { + "text": "At the Computer History Museum in California", + "isCorrect": false + }, + { + "text": "It was lost when the Mark II was scrapped", + "isCorrect": false + } + ], + "explanation": "The page, with the moth still taped on, belongs to the Smithsonian's National Museum of American History in Washington, D.C.\n\nIt took a detour to get there. The log stayed with the Navy, and a 1988 Navy photo record places it in the computer museum of the Naval Surface Warfare Center in Dahlgren, Virginia. The Smithsonian's catalog record is also the source of the corrections in the previous Questions: it says the log was probably not Hopper's and that the word \"bug\" was already in use.\n\nMuseum records are a good place to check a famous object's story. Curators document where the object came from and what it can and can't show.", + "references": "[Smithsonian National Museum of American History: Log book with computer bug](https://americanhistory.si.edu/collections/object/nmah_334663), [Naval History and Heritage Command: NH 96566-KN, The first \"computer bug\"](https://www.history.navy.mil/our-collections/photography/numerical-list-of-images/nhhc-series/nh-series/NH-96000/NH-96566-KN.html)" + }, + { + "id": "incognito-who-can-still-see", + "title": "You browse in Chrome's Incognito mode. Who may still be able to see which sites you visit?", + "type": "multiple-choice", + "options": [ + { + "text": "The websites you visit", + "isCorrect": true + }, + { + "text": "Your employer or school, if they manage the network you're on", + "isCorrect": true + }, + { + "text": "Your internet service provider", + "isCorrect": true + }, + { + "text": "Someone who later opens Chrome's browsing history on the same computer", + "isCorrect": false + } + ], + "explanation": "Incognito mode changes what your **own browser** keeps. When you close all Incognito windows, Chrome deletes the history, cookies and site data from that session. It keeps files you downloaded and bookmarks you saved. So someone who uses your computer later won't find those sites in Chrome's history.\n\nIt doesn't hide your traffic from anyone else. Chrome's help page says so (as of 2026): the websites you visit, and whoever manages your network, such as your school, employer or internet service provider, may be able to see your activity. Your requests travel over the same network, from the same IP address, to the same servers. Sites can still recognize you if you sign in.\n\nThe name suggests invisibility. The feature is closer to \"don't keep a record on this device\", and every major browser's private mode works the same way.\n\nTry it: open an Incognito window and read the notice on the new tab page. It lists who can still see what you do.", + "references": "[Google Chrome Help: Browse in Incognito mode](https://support.google.com/chrome/answer/95464), [Wikipedia: Private browsing](https://en.wikipedia.org/wiki/Private_browsing)" + }, + { + "id": "qwerty-designed-to-slow-typists", + "title": "Was the QWERTY keyboard layout designed to slow typists down?", + "type": "single-choice", + "options": [ + { + "text": "There's no good evidence for that. The layout changed over years of prototypes, and historians still argue about why", + "isCorrect": true + }, + { + "text": "Yes. Christopher Sholes admitted it in his patent", + "isCorrect": false + }, + { + "text": "Yes. Fast typists jammed the early machines, so the layout was made deliberately awkward", + "isCorrect": false + }, + { + "text": "No. It was scientifically optimized for speed, and no layout beats it", + "isCorrect": false + } + ], + "explanation": "\"Designed to slow typists down\" is a garbled version of one historical account, and historians dispute that account too.\n\n- **The jamming account.** On early typewriters, typebars could collide when neighboring keys were struck quickly one after another. In this version, Christopher Sholes split up some common letter pairs to reduce jams. Even if that's true, the goal was fewer jams, not slower typing.\n- **The telegraph account.** Koichi and Motoko Yasuoka of Kyoto University traced the layout through Sholes's prototypes. They argue that early users shaped it, especially telegraph operators transcribing Morse code, and that the arrangement changed often and grew into QWERTY \"among different requirements\". A layout that slowed operators down would have been useless to people keeping up with an incoming message.\n\nBoth accounts agree that nobody planned QWERTY in one step from a single principle, and neither supports a deliberate slowdown. It survives today because of path dependence: once millions of people had learned it, switching cost more than any gain was worth.\n\nThe evidence here comes from piecing history together from patents and prototypes. That's why the honest answer is \"disputed\" instead of a new, equally tidy story.", + "references": "[Smithsonian Magazine: Fact or fiction? The legend of the QWERTY keyboard (2013)](https://www.smithsonianmag.com/arts-culture/fact-of-fiction-the-legend-of-the-qwerty-keyboard-49863249/), [Yasuoka and Yasuoka (2011): On the prehistory of QWERTY](https://repository.kulib.kyoto-u.ac.jp/handle/2433/139379), [Wikipedia: QWERTY](https://en.wikipedia.org/wiki/QWERTY)" + }, + { + "id": "closing-iphone-apps-battery", + "title": "Does swiping away your open iPhone apps save battery?", + "type": "single-choice", + "options": [ + { + "text": "No. Apple says to close an app only when it stops responding, and suspended apps use almost no power", + "isCorrect": true + }, + { + "text": "Yes. Every app in the switcher keeps running and draining the battery until you close it", + "isCorrect": false + }, + { + "text": "Yes, but only on iPhones older than five years", + "isCorrect": false + }, + { + "text": "No, because iOS already quits every app the moment you leave it", + "isCorrect": false + } + ], + "explanation": "The app switcher shows apps you used recently, not apps that are running. When you leave an app, iOS usually suspends it: the app stays in memory but gets no processor time, so it uses almost no power. If the system needs the memory, it removes the app on its own.\n\nApple's support page says it directly (as of 2026): close an app only if it's unresponsive. In 2016, a customer emailed Tim Cook to ask whether he quit his apps and whether that was necessary for battery life. Craig Federighi, Apple's head of software, replied: \"No and no.\"\n\nThe last Option is wrong too, because iOS doesn't quit apps the moment you leave. Some apps keep working in the background, such as navigation, music, calls and downloads. Those can use battery, and **Settings → Battery** shows you which ones. Check that list instead of swiping everything away.", + "references": "[Apple Support: Close an app on your iPhone or iPod touch](https://support.apple.com/en-us/109359), [9to5Mac: Apple's software SVP says quitting multitasking apps not necessary (2016)](https://9to5mac.com/2016/03/10/should-you-quit-ios-apps-answer/)" + }, + { + "id": "boss-which-stories-are-true", + "title": "Final round: which of these tech stories are true?", + "type": "multiple-choice", + "options": [ + { + "text": "Operators taped a real moth into a Harvard Mark II logbook in 1947", + "isCorrect": true + }, + { + "text": "NASA once paid $128.89 each for mechanical pencils", + "isCorrect": true + }, + { + "text": "In *Civilization V*, Gandhi was deliberately given a maximum preference for nuclear weapons, as a joke", + "isCorrect": true + }, + { + "text": "The 404 error is named after an office at CERN", + "isCorrect": false + }, + { + "text": "Bill Gates said in 1981 that 640K ought to be enough for anybody", + "isCorrect": false + }, + { + "text": "NASA spent millions developing a space pen while the Soviets used pencils", + "isCorrect": false + } + ], + "explanation": "Three are true and three are myths, and each true story sits right next to a myth.\n\n- **The moth is real.** The page exists, and the Smithsonian has it. The myth is what people add: that it gave us the word \"bug\", or that it was Hopper's log.\n- **The $128.89 pencils are real.** NASA bought 34 of them in 1965, and the outcry helped push it toward something cheaper. The myth is that NASA then spent millions on a pen. Fisher paid for the pen, and NASA bought it for $2.39.\n- **Civilization V's Gandhi is real.** Jon Shafer set Gandhi's nuke ratings to the maximum on purpose in 2010. The myth is the 1991 underflow bug people think it refers to.\n- **Room 404, the 1981 Gates quote and the millions-for-a-pen story** have no source from the time, and the people involved or the purchase records contradict them.\n\nThe myths in this Quiz didn't come from nothing. Each one started from a real object, person or mechanism and added a punchline. To tell them apart, ask for a source from the time the thing supposedly happened.", + "references": "[Scientific American: Fact or fiction? NASA spent millions to develop a pen that would write in space (2006)](https://www.scientificamerican.com/article/fact-or-fiction-nasa-spen/), [Wikipedia: Nuclear Gandhi](https://en.wikipedia.org/wiki/Nuclear_Gandhi), [Wired: Page not found — a brief history of the 404 error (2017)](https://www.wired.com/story/page-not-found-a-brief-history-of-the-404-error/), [Quote Investigator: 640K of computer memory is more than enough for anyone (2011)](https://quoteinvestigator.com/2011/09/08/640k-enough/)" + } + ] +} diff --git a/content/quizzes/tech-myths-that-wont-die/640k-quote-paper-trail.svg b/content/quizzes/tech-myths-that-wont-die/640k-quote-paper-trail.svg new file mode 100644 index 0000000..df3a760 --- /dev/null +++ b/content/quizzes/tech-myths-that-wont-die/640k-quote-paper-trail.svg @@ -0,0 +1,69 @@ + + + + + The paper trail of "640K ought to be enough" + Sources traced by Quote Investigator + + + 1981 + Supposedly said + The IBM PC launches. Nobody has found a source from 1981. + + 1985 + First in print + InfoWorld, 29 April: a line credited to Gates about setting the PC-DOS limit + at 640K and thinking "nobody would ever need that much memory". + + 1990 + The famous wording + InfoWorld, 1 January: "640K ought to be enough for anyone." + Five years after the first version and nine years after 1981. + + 1996 + Denied + Gates, in his newspaper column: there's "never a citation", + and the line "floats like a rumor". + diff --git a/content/quizzes/tech-myths-that-wont-die/apollo-7-cunningham-writing-with-space-pen.webp b/content/quizzes/tech-myths-that-wont-die/apollo-7-cunningham-writing-with-space-pen.webp new file mode 100644 index 0000000..f813cb5 Binary files /dev/null and b/content/quizzes/tech-myths-that-wont-die/apollo-7-cunningham-writing-with-space-pen.webp differ diff --git a/content/quizzes/tech-myths-that-wont-die/ibm-pc-memory-map-640k.svg b/content/quizzes/tech-myths-that-wont-die/ibm-pc-memory-map-640k.svg new file mode 100644 index 0000000..61da816 --- /dev/null +++ b/content/quizzes/tech-myths-that-wont-die/ibm-pc-memory-map-640k.svg @@ -0,0 +1,75 @@ + + + + + Where 640K comes from: the IBM PC memory map + The 8088 has 20 address lines: 2²⁰ bytes = 1,024 KB of addresses + + RAM for DOS and programs + 0–640 KB + + Video + 640–768 + + ROMs, reserved + 768–960 + + BIOS + 960+ + + 0 + + 640 KB (address A0000 hex) + + 1,024 KB + + Conventional memory: 640 KB + + Upper area: 384 KB + + In 1981, IBM put video memory and ROMs at the top of the address space. + Programs got the one continuous block of RAM below them, and extra RAM + above 640 KB couldn't extend it. The hardware layout set the limit. + diff --git a/content/quizzes/tech-myths-that-wont-die/mark-ii-log-page-with-moth-1947.webp b/content/quizzes/tech-myths-that-wont-die/mark-ii-log-page-with-moth-1947.webp new file mode 100644 index 0000000..3b8c088 Binary files /dev/null and b/content/quizzes/tech-myths-that-wont-die/mark-ii-log-page-with-moth-1947.webp differ diff --git a/content/quizzes/tech-myths-that-wont-die/unsigned-8-bit-underflow-wheel.svg b/content/quizzes/tech-myths-that-wont-die/unsigned-8-bit-underflow-wheel.svg new file mode 100644 index 0000000..ac68c6c --- /dev/null +++ b/content/quizzes/tech-myths-that-wont-die/unsigned-8-bit-underflow-wheel.svg @@ -0,0 +1,93 @@ + + + + + An unsigned 8-bit counter wraps around + 256 values on a ring, 0 to 255. Dots stand in for the values in between. + + + 0 + + 1 + + 2 + + + 64 + + + + + 128 + + + + + 192 + + + 254 + + 255 + + + + + + + 1 − 2: two steps back + 8 bits + 2⁸ = 256 values + + Unsigned 8-bit (0 to 255) + 1 − 1 = 0 + 0 − 1 wraps to the top: 255 + So 1 − 2 = 255 + + Signed 8-bit (−128 to 127) + Same bits, read differently: + the pattern for 255 means −1 + So 1 − 2 = −1, as expected + diff --git a/content/quizzes/the-internet-runs-on-cats.json b/content/quizzes/the-internet-runs-on-cats.json new file mode 100644 index 0000000..ba97382 --- /dev/null +++ b/content/quizzes/the-internet-runs-on-cats.json @@ -0,0 +1,519 @@ +{ + "schemaVersion": 1, + "id": "the-internet-runs-on-cats", + "title": "The internet runs on cats: what the research says", + "description": "Cats are the internet's unofficial mascot, and the programmer with a cat on the desk is a stereotype. But do tech people really prefer cats? Along the way you'll meet the neural network that learned \"cat\" without anyone telling it what a cat was, a survey of nearly 7,000 people about why they watch cat videos, an Ig Nobel prize for asking whether cats are liquid, and software that locks your keyboard when a cat walks on it. Each Question sorts the fun claim from what the evidence supports.", + "language": "en", + "tags": ["psychology", "critical-thinking", "science", "internet-culture"], + "author": "Quizbun", + "questions": [ + { + "id": "developers-prefer-cats-evidence", + "title": "Is there solid evidence that people in IT like cats more than other people do?", + "type": "single-choice", + "options": [ + { + "text": "No. The largest developer survey we found leaned toward dogs, and it had no non-developers to compare with", + "isCorrect": true + }, + { + "text": "Yes. Stack Overflow's developer survey shows it every year", + "isCorrect": false + }, + { + "text": "Yes. A 2014 personality study of programmers proved it", + "isCorrect": false + }, + { + "text": "No. Studies show IT people are allergic to cats more often", + "isCorrect": false + } + ], + "explanation": "Stack Overflow asked \"Dogs or cats?\" in its 2016 Developer Survey. Of the 45,239 respondents who answered, 48.9% picked dogs, 34.8% cats and 16.4% something else. Germany went the other way, with cats ahead of dogs.\n\nEven so, that survey can't answer this Question. \"More than other people\" needs a comparison, and nobody outside software answered the same survey. A dog-leaning result among developers fits with developers liking cats *more* than the general public, *less*, or the same. We didn't find any large study that compares pet preference across jobs. That doesn't prove no such study exists, so read the stereotype as unproven rather than disproved.\n\nAs far as we can tell, it's a cultural joke with no evidence behind it.", + "references": "[Stack Overflow Developer Survey 2016: overview](https://survey.stackoverflow.co/2016/), [Stack Overflow 2016 raw results (CSV, column `dogs_vs_cats`)](https://github.com/StackExchange/Survey/tree/main/packages/archive/2016)", + "images": [ + { + "src": "stack-overflow-2016-dogs-vs-cats.svg", + "alt": "Bar chart of the 2016 Stack Overflow survey question \"Dogs or cats?\". All 45,239 respondents: dogs 48.9%, cats 34.8%, other 16.4%. Germany, 3,154 respondents: dogs 38.4%, cats 44.5%, other 17.0%. A note says only developers answered, so the chart cannot compare developers with anyone else.", + "caption": "Percentages computed from Stack Overflow's published 2016 raw data, counting only people who answered the question. Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 410 + } + ] + }, + { + "id": "study-that-would-settle-it", + "title": "What kind of study could show whether IT workers like cats more than other people do?", + "type": "single-choice", + "options": [ + { + "text": "A representative survey of many occupations that asks everyone the same pet question, then compares IT workers with the rest", + "isCorrect": true + }, + { + "text": "A poll on a programming forum asking members whether they like cats", + "isCorrect": false + }, + { + "text": "Counting cat photos in developers' social media posts", + "isCorrect": false + }, + { + "text": "Interviewing a dozen well-known programmers about their pets", + "isCorrect": false + } + ], + "explanation": "The claim compares two groups, so the study needs both groups, measured the same way.\n\n- **A comparison group.** A forum poll only reaches programmers. Even a perfect result tells you what programmers like, not whether they like it *more*.\n- **Representative sampling.** People decide for themselves whether to answer a forum poll or post cat photos, and cat lovers are more likely to do both. That inflates the result.\n- **The same measure for everyone.** Counting photos measures posting habits. Cats may just be easier than dogs to photograph at a desk.\n\nInterviewing famous programmers gives you a handful of memorable cases, which is how stereotypes form in the first place. Only the first design gets you past anecdotes.", + "references": "[Wikipedia: Self-selection bias](https://en.wikipedia.org/wiki/Self-selection_bias), [Wikipedia: Sampling (statistics)](https://en.wikipedia.org/wiki/Sampling_(statistics))" + }, + { + "id": "cat-people-introvert-study-limits", + "title": "A 2014 study reported that self-described \"cat people\" were more introverted than \"dog people\". Which of these limit what it can tell us about programmers?", + "description": "Psychologist Denise Guastello of Carroll University surveyed about 600 college students on their pet preference and personality.", + "type": "multiple-choice", + "options": [ + { + "text": "The participants were college students, not workers in IT or any other field", + "isCorrect": true + }, + { + "text": "Pet preference and personality were both self-reported on questionnaires", + "isCorrect": true + }, + { + "text": "It found an association, which can't show whether cats make people introverted, introverts choose cats, or neither", + "isCorrect": true + }, + { + "text": "About 600 people is too few for any statistical test to work", + "isCorrect": false + }, + { + "text": "Introversion was measured with brain scans, which are known to be unreliable", + "isCorrect": false + } + ], + "explanation": "The study asked students whether they saw themselves as dog lovers or cat lovers, then gave them personality questionnaires. Dog lovers scored as more energetic and outgoing. Cat lovers scored as more introverted, open-minded and sensitive.\n\nGetting from there to \"programmers like cats\" takes two leaps the data doesn't support. First, students aren't programmers, and the study didn't ask about jobs. Second, even if programmers are more introverted on average, a link between introversion and cats among students doesn't automatically hold for a different group.\n\nSix hundred people is plenty to detect a moderate difference between groups, so sample size isn't the issue. The issues are *who* took part and *what kind* of evidence it is: self-reported and correlational. Nobody scanned a brain or watched how anyone behaved.\n\nSo the evidence that cat people are more introverted comes down to one correlational survey of students.", + "references": "[Wikipedia: Cat people and dog people](https://en.wikipedia.org/wiki/Cat_people_and_dog_people), [CBS News: \"Dog people\" and \"cat people\" have different personality traits (2014)](https://www.cbsnews.com/news/dog-people-and-cat-people-have-different-personality-traits-study-finds/), [Wikipedia: Correlation does not imply causation](https://en.wikipedia.org/wiki/Correlation_does_not_imply_causation)" + }, + { + "id": "google-cat-network-labels", + "title": "In 2012, a Google network learned to detect cats from YouTube stills. How many of its training images were labeled \"cat\"?", + "type": "single-choice", + "options": [ + { + "text": "None", + "isCorrect": true + }, + { + "text": "About 10,000", + "isCorrect": false + }, + { + "text": "About half of them", + "isCorrect": false + }, + { + "text": "All of them, because it was only shown cats", + "isCorrect": false + } + ], + "explanation": "None at all. The training set was 10 million frames, one from each of 10 million YouTube videos, with no labels of any kind. This is called **unsupervised learning**.\n\nThe network was a sparse autoencoder. Its only job was to rebuild each input image after squeezing it through layers that could keep just a few features active at a time. To do that well on YouTube frames, it had to find patterns that show up often. Human faces are one, and cat faces turned out to be another. After training, the researchers searched the top layer for neurons that reacted to test images of cats, and one reacted strongly. Nobody told the network what a cat was. It picked up the concept from the data.\n\nLabels came in only for a separate test: recognizing 22,000 ImageNet categories. There, the researchers used labeled examples to train a classifier on top of the features the network had learned.", + "references": "[Le et al. (2012): Building high-level features using large scale unsupervised learning](https://arxiv.org/abs/1112.6209), [Google blog: Using large-scale brain simulations for machine learning and A.I. (2012)](https://blog.google/technology/ai/using-large-scale-brain-simulations-for/), [Wikipedia: Autoencoder](https://en.wikipedia.org/wiki/Autoencoder)", + "images": [ + { + "src": "unlabeled-frames-to-cat-neuron.svg", + "alt": "Three boxes joined by arrows. First: 10 million frames, one per YouTube video, 200 by 200 pixels, no labels at all. Second: a 9-layer sparse autoencoder with about 1 billion connections, trained on 16,000 cores for 3 days. Third: one top-layer neuron fires strongly for cat faces, with others for human faces and bodies. A note says the training goal was to rebuild each input image from a compressed, sparse code.", + "caption": "Figures from Le et al. (2012). Diagram drawn for this Quiz; the paper's own visualization of the cat neuron is linked in References rather than copied.", + "placement": "explanation", + "width": 720, + "height": 306 + } + ] + }, + { + "id": "google-cat-network-hardware", + "title": "What hardware did Google's 2012 cat-detecting network train on?", + "type": "single-choice", + "options": [ + { + "text": "A cluster of 1,000 machines with 16,000 CPU cores, for three days", + "isCorrect": true + }, + { + "text": "A single high-end gaming GPU, overnight", + "isCorrect": false + }, + { + "text": "Sixteen machines with one GPU each, for a month", + "isCorrect": false + }, + { + "text": "A laptop, because the network had only a few thousand connections", + "isCorrect": false + } + ], + "explanation": "The model had about 1 billion connections and trained on 10 million images of 200 × 200 pixels. That was too big for one machine, so the team split the model across machines (model parallelism). They also ran many copies at once, each sending updates to shared parameter servers (asynchronous SGD). The whole job took 1,000 machines and 16,000 CPU cores for three days.\n\nThe GPU answers are tempting because GPUs run almost all deep learning today. In 2012 they were only starting to take over. That same year, AlexNet won the ImageNet competition on two GPUs, and that result did far more to change how people train networks.\n\nThe team went this big to answer the paper's main question: with a large enough model and dataset, can you learn high-level concepts from unlabeled data at all?", + "references": "[Le et al. (2012), abstract and Section 3](https://arxiv.org/abs/1112.6209), [Wikipedia: AlexNet](https://en.wikipedia.org/wiki/AlexNet)" + }, + { + "id": "cat-detector-free-accuracy", + "title": "The cat test set was about 35% cat faces and 65% other images. What accuracy, in percent, does a lazy classifier get by answering \"not a cat\" every time?", + "description": "The paper's face test had 13,026 positives among 37,000 images, and the cat test used the same ratio. Round to one decimal place.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [64.8], + "tolerance": 0.1 + }, + "explanation": "Using the face test's counts, 23,974 negatives ÷ 37,000 images ≈ 0.648. Answering \"not a cat\" every time scores **64.8%** without looking at a single pixel. The paper lists exactly this as its \"random guess\" baseline.\n\nKeep that number in mind when you hear about the famous cat neuron. Its best accuracy was **74.8%**, about 10 points above doing nothing and 7.6 points above the best simple linear filter (67.2%). The face neuron did better, at 81.7%, and a human-body neuron reached 76.7%.\n\nFor a network that had never seen a label, that was an impressive result in 2012. Still, a headline accuracy means little until you know what the do-nothing baseline scores. Whenever a test has unbalanced classes, check the baseline first.\n\nThe evidence that the network learned a cat concept is strong. As a detector, though, it was modest and far from reliable.", + "references": "[Le et al. (2012), Section 5 and Table 1](https://arxiv.org/abs/1112.6209), [Wikipedia: Accuracy paradox](https://en.wikipedia.org/wiki/Accuracy_paradox)", + "images": [ + { + "src": "cat-detector-vs-baseline.svg", + "alt": "Bar chart on a 0 to 100 percent axis. Always answering \"not a cat\": 64.8%. Best simple linear filter: 67.2%. Best cat neuron: 74.8%. A dashed line at 64.8% marks accuracy that needs no learning.", + "caption": "Numbers from Table 1 of Le et al. (2012). Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 318 + } + ] + }, + { + "id": "google-network-other-concepts", + "title": "Besides cat faces, which concepts did the 2012 paper report neurons responding to?", + "type": "multiple-choice", + "options": [ + { + "text": "Human faces", + "isCorrect": true + }, + { + "text": "Human bodies", + "isCorrect": true + }, + { + "text": "Dogs", + "isCorrect": false + }, + { + "text": "Written text", + "isCorrect": false + } + ], + "explanation": "The paper is mostly about **faces**. It started by asking whether you could learn a face detector from unlabeled images, and its best face neuron scored 81.7%. The authors then checked whether other things common on YouTube had been learned too. They built two more test sets, found a **cat-face** neuron (74.8%) and a **human-body** neuron (76.7%), and stopped there.\n\nThe paper doesn't mention dogs or text because the authors never tested for them. That's different from finding that the network didn't learn them. A dog neuron may or may not have existed; nobody looked.\n\nCats made the headlines because they're funnier than faces. Before you retell a result, check what the research was mainly about.", + "references": "[Le et al. (2012), Sections 4 and 5](https://arxiv.org/abs/1112.6209), [NPR: A massive Google network learns to identify — cats (2012)](https://www.npr.org/2012/06/26/155792609/a-massive-google-network-learns-to-identify)" + }, + { + "id": "cat-video-survey-recruitment", + "title": "In 2015, media researcher Jessica Myrick surveyed nearly 7,000 people about watching cat videos online. How did the survey reach them?", + "type": "single-choice", + "options": [ + { + "text": "Mostly through social media, helped by the owner of Lil Bub, a celebrity internet cat", + "isCorrect": true + }, + { + "text": "A random telephone sample of U.S. households", + "isCorrect": false + }, + { + "text": "Students in an introductory psychology course", + "isCorrect": false + }, + { + "text": "Volunteers randomly assigned to watch cat or dog videos in a lab", + "isCorrect": false + } + ], + "explanation": "Lil Bub's owner, Mike Bridavsky, helped spread the survey on social media, and 6,795 people answered. That got a large sample quickly, but it's a sample of people who follow a famous cat online.\n\nKeep that in mind for everything the survey found. A large sample shrinks random error, but it doesn't fix **selection bias**. Seven thousand cat fans can tell you a lot about cat fans and much less about everyone else. Only the lab option could test cause and effect. Random assignment is what separates \"cat videos change mood\" from \"people in a certain mood look for cat videos\", and this study didn't use it.\n\nMyrick's study is a useful published survey. Just remember who answered it as you go through the next three Questions.", + "references": "[Myrick (2015), Computers in Human Behavior 52: Emotion regulation, procrastination, and watching cat videos online](https://doi.org/10.1016/j.chb.2015.06.001), [ScienceDaily: Viewing cat videos boosts energy, positive emotions (2015)](https://www.sciencedaily.com/releases/2015/06/150616093357.htm), [Wikipedia: Selection bias](https://en.wikipedia.org/wiki/Selection_bias)" + }, + { + "id": "cat-video-mood-effects", + "title": "What did respondents in Myrick's survey report about how they felt after watching cat videos?", + "type": "multiple-choice", + "options": [ + { + "text": "More energetic", + "isCorrect": true + }, + { + "text": "More positive", + "isCorrect": true + }, + { + "text": "Less anxious, annoyed and sad", + "isCorrect": true + }, + { + "text": "Measurably more productive at work afterward", + "isCorrect": false + }, + { + "text": "A lower heart rate", + "isCorrect": false + } + ], + "explanation": "Respondents said they felt more energetic and more positive after watching cat videos than before. They also reported fewer negative emotions such as anxiety, annoyance and sadness.\n\nNotice the word *reported*. These were answers to a one-time online survey. Nobody took anyone's pulse or measured their work output. The press release suggests the mood boost *may* help people take on hard tasks afterward, but that's a hypothesis the study didn't test.\n\nSurveys like this are good for learning how people experience something and weak for learning what it does to them. Here, the evidence that cat videos lift mood is self-reported and comes from a self-selected group of cat fans.", + "references": "[ScienceDaily: Viewing cat videos boosts energy, positive emotions (2015)](https://www.sciencedaily.com/releases/2015/06/150616093357.htm), [Wikipedia: Self-report study](https://en.wikipedia.org/wiki/Self-report_study)" + }, + { + "id": "cat-video-guilt", + "title": "Many respondents watched cat videos at work or while studying. How did they describe the guilt of procrastinating?", + "type": "single-choice", + "options": [ + { + "text": "They felt some guilt, but the enjoyment outweighed it", + "isCorrect": true + }, + { + "text": "They felt no guilt at all", + "isCorrect": false + }, + { + "text": "The guilt outweighed the enjoyment, so they felt worse overall", + "isCorrect": false + }, + { + "text": "The survey didn't ask about guilt", + "isCorrect": false + } + ], + "explanation": "The survey asked about guilt because the study was as much about procrastination and managing emotions as about cats. Respondents did feel guilty about watching when they should have been working or studying, but they said the fun outweighed the guilt.\n\nMost of this viewing was passive. Only about a quarter of the cat videos people watched were ones they had searched for. They stumbled on the rest in feeds and on sites like Facebook, YouTube and BuzzFeed.\n\nIf you're reading this at work, you're in good company, according to people who were also reading about cats at work.", + "references": "[ScienceDaily: Viewing cat videos boosts energy, positive emotions (2015)](https://www.sciencedaily.com/releases/2015/06/150616093357.htm), [Wikipedia: Procrastination](https://en.wikipedia.org/wiki/Procrastination)" + }, + { + "id": "who-watches-cat-videos", + "title": "According to Myrick's survey, who was more likely to watch cat videos?", + "type": "multiple-choice", + "options": [ + { + "text": "People who own or have owned cats", + "isCorrect": true + }, + { + "text": "People who scored higher on agreeableness", + "isCorrect": true + }, + { + "text": "People who scored higher on shyness", + "isCorrect": true + }, + { + "text": "People who work in IT", + "isCorrect": false + } + ], + "explanation": "Current and past cat owners watched more, and so did respondents who scored higher on agreeableness and shyness. About 36% of respondents called themselves \"cat people\", and 60% said they liked both cats and dogs.\n\nThe IT option is the stereotype sneaking back in. The published summaries of the study say nothing about jobs, so the survey can neither support nor rule it out.\n\nThese are associations within an unusual sample. \"Shy people watch more cat videos\" is a fair summary of the data. \"Cat videos attract shy people\" and \"cat videos make people shy\" are cause-and-effect stories this design can't tell apart.", + "references": "[ScienceDaily: Viewing cat videos boosts energy, positive emotions (2015)](https://www.sciencedaily.com/releases/2015/06/150616093357.htm), [Wikipedia: Agreeableness](https://en.wikipedia.org/wiki/Agreeableness)" + }, + { + "id": "are-cats-liquid", + "title": "A physicist asked whether cats are liquids. What decides the answer?", + "type": "single-choice", + "options": [ + { + "text": "How long you watch: over minutes a cat flows to fill its container, but in a snapshot it keeps its shape", + "isCorrect": true + }, + { + "text": "The cat's body temperature", + "isCorrect": false + }, + { + "text": "Whether the container is glass or ceramic", + "isCorrect": false + }, + { + "text": "Nothing. The paper was a joke with no physics in it", + "isCorrect": false + } + ], + "explanation": "Marc-Antoine Fardin studies rheology, the science of how materials flow and change shape. He started from a practical definition: a liquid changes its shape to fit its container. Plenty of photos online show cats doing exactly that in sinks, boxes and jars.\n\nWhether something counts as a liquid depends on the **timescale**. Every material has a *relaxation time*, roughly how long it takes to adapt its shape. Divide that by how long you watch and you get the **Deborah number**, *De*. Below 1, the material flows while you watch, so it behaves like a liquid. Above 1, it keeps its shape, so it behaves like a solid. Glaciers flow over years. Silly Putty bounces when you throw it but slumps into a puddle over hours.\n\nA cat that settles into a sink over a few minutes is a liquid on that timescale. The same cat in a 1/100 s photo is a solid.\n\nDon't fall for the last Option. Fardin's paper, *On the rheology of cats* (Rheology Bulletin, 2014), is playful, but the rheology in it is real, and it won the **2017 Ig Nobel Prize in Physics**.\n\nSo are cats liquid? It depends on how long you watch, and that's the whole point.", + "references": "[Fardin in The Conversation: Answering the question that won me the Ig Nobel Prize: are cats liquid?](https://theconversation.com/answering-the-question-that-won-me-the-ig-nobel-prize-are-cats-liquid-86589), [Fardin (2014): On the rheology of cats, annotated at Fermat's Library](https://fermatslibrary.com/s/on-the-rheology-of-cats), [Wikipedia: Deborah number](https://en.wikipedia.org/wiki/Deborah_number)", + "images": [ + { + "src": "cat-curled-in-sink.webp", + "alt": "A tabby-and-white cat asleep in a bathroom sink, curled so tightly that its body follows the round curve of the basin.", + "caption": "Photo by Anagoria, [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:20190410_230530_Cat_Lissy_in_Bathroom_Sink_anagoria.jpg).", + "width": 1280, + "height": 1280 + }, + { + "src": "deborah-number-scale.svg", + "alt": "A logarithmic scale of the Deborah number from 0.001 to 10,000, split at 1. Left of 1 is labeled \"it flows (liquid)\", right of 1 \"it keeps its shape (solid)\". A cat napping in a sink for an hour sits at about 0.017, on the liquid side. A cat in a 1/100 second photo sits at 6,000, on the solid side. A note says the example assumes a cat takes about 1 minute to settle into a container.", + "caption": "De = relaxation time ÷ observation time. The one-minute relaxation time is an assumption for illustration. Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 366 + } + ] + }, + { + "id": "kitten-deborah-number", + "title": "Suppose a kitten takes about 1 minute to settle into the shape of a box. If you watch it for 10 seconds, is it a liquid or a solid?", + "description": "Use De = relaxation time ÷ observation time, where De < 1 means it flows and De > 1 means it keeps its shape.", + "type": "single-choice", + "options": [ + { + "text": "A solid: De = 60 ÷ 10 = 6, so it can't change shape in time", + "isCorrect": true + }, + { + "text": "A liquid: De = 10 ÷ 60 ≈ 0.17, so it flows", + "isCorrect": false + }, + { + "text": "A liquid, because kittens are softer than adult cats", + "isCorrect": false + }, + { + "text": "Neither: De is exactly 1 for any living animal", + "isCorrect": false + } + ], + "explanation": "Relaxation time goes on top: 60 s ÷ 10 s = **6**. That's greater than 1, so in a 10-second look the kitten doesn't have time to change shape. On that timescale, it's a solid.\n\nThe tempting wrong answer flips the fraction. Both numbers are in seconds, so the units won't catch the mistake. To remember the order, think of the extremes. A very long observation should push De toward zero and let everything flow, like mountains over geological time. That only works when observation time is on the bottom.\n\nWatch the same kitten for an hour and De = 60 ÷ 3,600 ≈ 0.017, so it's a liquid. The kitten stayed the same; only your watching time changed.", + "references": "[Wikipedia: Deborah number](https://en.wikipedia.org/wiki/Deborah_number), [Fardin in The Conversation: are cats liquid?](https://theconversation.com/answering-the-question-that-won-me-the-ig-nobel-prize-are-cats-liquid-86589)", + "images": [ + { + "src": "kitten-in-box.webp", + "alt": "A black kitten lying in a shallow cardboard box on tissue paper, its body filling one end of the box.", + "caption": "Photo by Catboy69, [CC0](https://creativecommons.org/publicdomain/zero/1.0/), via [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:Cat_in_a_box_3.jpg).", + "width": 1280, + "height": 1139 + } + ] + }, + { + "id": "pawsense-detection", + "title": "PawSense is software that notices when a cat walks across your keyboard. How does it tell a cat from a person?", + "type": "single-choice", + "options": [ + { + "text": "It analyzes which keys are pressed together and the timing between presses", + "isCorrect": true + }, + { + "text": "It watches through the webcam for fur", + "isCorrect": false + }, + { + "text": "It detects body heat through the keys", + "isCorrect": false + }, + { + "text": "It listens through the microphone for purring", + "isCorrect": false + } + ], + "explanation": "PawSense works with the only data a keyboard gives it: which keys went down, in what combinations, and when. People press one key at a time, mostly in word-like bursts. A paw presses a clump of neighboring keys almost at once, with a rhythm no typist produces. Its maker says it usually spots a cat within one or two pawsteps.\n\nOnce it spots one, PawSense blocks the cat's input so the random keypresses never reach your programs. It also plays a sound that annoys cats, so they learn to stay off the keyboard even when nobody is around.\n\nThis is classification from behavior. PawSense needs no camera or special hardware, only patterns in ordinary input. Fraud detection and bot detection often work the same way, using typing rhythm and click timing.", + "references": "[BitBoost Systems: PawSense](https://www.bitboost.com/pawsense/), [Wikipedia: Keystroke dynamics](https://en.wikipedia.org/wiki/Keystroke_dynamics)", + "images": [ + { + "src": "paw-vs-human-keypresses.svg", + "alt": "Two keyboards side by side. On the left, a person typing \"cat\" presses C, then A, then T, one key at a time and far apart; the timeline below shows three evenly spaced key-down events. On the right, a paw presses six neighboring keys, D, F, G, C, V and B, at almost the same moment; its timeline shows a tight burst of events.", + "caption": "An illustration of the kind of signal PawSense reads, not its actual algorithm. Drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 390 + } + ] + }, + { + "id": "ig-nobel-aim", + "title": "PawSense won the Ig Nobel Prize in Computer Science in 2000. What are the Ig Nobel Prizes for?", + "type": "single-choice", + "options": [ + { + "text": "Achievements that first make people laugh, and then make them think", + "isCorrect": true + }, + { + "text": "Mocking research that is useless or fraudulent", + "isCorrect": false + }, + { + "text": "Research that failed peer review but deserved a second look", + "isCorrect": false + }, + { + "text": "The funniest papers, as chosen by the Nobel Foundation", + "isCorrect": false + } + ], + "explanation": "The Ig Nobels \"honor achievements that first make people laugh, and then make them think\". The *Annals of Improbable Research* has run them since 1991, and they have nothing to do with the Nobel Foundation.\n\nMost winners are real, published work with an odd or funny angle. Chris Niswander won for PawSense in 2000, and Marc-Antoine Fardin's rheology of cats won the Physics prize in 2017. You laugh at the question, then start thinking once you see someone take it seriously: timescales in materials for Fardin, classification from input patterns for PawSense.\n\nThe mocking Option isn't entirely wrong. A few prizes have gone out ironically, as criticism. Those are exceptions, and neither cat prize is one of them.", + "references": "[Wikipedia: Ig Nobel Prize](https://en.wikipedia.org/wiki/Ig_Nobel_Prize), [Wikipedia: List of Ig Nobel Prize winners](https://en.wikipedia.org/wiki/List_of_Ig_Nobel_Prize_winners)" + }, + { + "id": "unix-cat-name", + "title": "The Unix `cat` command isn't named after the animal. Type the word it's short for.", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": ["concatenate", "concatenation", "catenate"] + }, + "explanation": "The manual page says so directly: `cat - concatenate files and print on the standard output`. *Concatenate* means to join things end to end, and that's the command's real job:\n\n```bash\ncat part1.txt part2.txt > whole.txt\n```\n\nPrinting one file with `cat notes.txt` is just concatenation with a single file.\n\nBecause `cat` is so familiar, people often pipe it into commands that could read the file on their own, like `cat log.txt | grep error` instead of `grep error log.txt`. Unix folklore calls this a \"useless use of cat\". It does no harm in a terminal, though it does start an extra process.\n\nTry it: open a terminal and run `man cat`.", + "references": "[man7.org: cat(1)](https://man7.org/linux/man-pages/man1/cat.1.html), [Wikipedia: cat (Unix)](https://en.wikipedia.org/wiki/Cat_(Unix))" + }, + { + "id": "octocat-original-name", + "title": "GitHub's mascot, the Octocat, began as stock clip art. What had its designer, Simon Oxley, called it?", + "type": "single-choice", + "options": [ + { + "text": "Octopuss", + "isCorrect": true + }, + { + "text": "Gitty", + "isCorrect": false + }, + { + "text": "Kraken Kitty", + "isCorrect": false + }, + { + "text": "Tentacat", + "isCorrect": false + } + ], + "explanation": "Oxley drew a cat's head with octopus tentacles and sold it on iStock as **Octopuss**, from *octopus* plus *pussycat*. GitHub's founders found it while looking for an image for the site, negotiated exclusive rights with Oxley, and renamed it the Octocat.\n\nIt's a good reminder that stock images come with license terms. Putting clip art on a website and building a brand around it are different uses, and the second one took an agreement with the artist.\n\nThe Octocat also adds one more cat to the \"programmers love cats\" story. It makes the stereotype fun and easy to see, and as the earlier Questions showed, seeing something often doesn't make it proven.", + "references": "[Wikipedia: Simon Oxley](https://en.wikipedia.org/wiki/Simon_Oxley), [Cameron McEfee: The Octocat](https://cameronmcefee.com/work/the-octocat/)" + }, + { + "id": "claims-supported-by-evidence", + "title": "Which of these claims does the evidence in this Quiz support?", + "type": "multiple-choice", + "options": [ + { + "text": "A neural network can learn to detect cat faces better than a do-nothing baseline without ever seeing a labeled cat", + "isCorrect": true + }, + { + "text": "In Stack Overflow's 2016 survey, more responding developers picked dogs than cats", + "isCorrect": true + }, + { + "text": "Whether a cat behaves as a liquid depends on how long you observe it", + "isCorrect": true + }, + { + "text": "Developers like cats more than the general public does", + "isCorrect": false + }, + { + "text": "Watching cat videos makes office workers more productive", + "isCorrect": false + }, + { + "text": "Owning a cat makes people more introverted", + "isCorrect": false + } + ], + "explanation": "Each supported claim rests on evidence that matches its wording.\n\n- **Cat detector.** A published experiment with a held-out test set. The best cat neuron scored 74.8% against a 64.8% baseline, a modest but real gain.\n- **Dogs versus cats.** Stack Overflow's own data, 48.9% to 34.8%. The claim stays inside what was measured: developers who answered one survey in 2016.\n- **Liquid cats.** The Deborah number follows from the definitions. Watch long enough and the cat flows; watch briefly and it doesn't.\n\nThe unsupported claims overreach in familiar ways.\n\n- **Developers versus the public.** This needs a comparison group, and no study we found has one.\n- **Productivity.** Nobody measured it. Myrick's respondents *reported* a better mood, and they were cat fans reached through a celebrity cat's followers.\n- **Cats cause introversion.** This turns a correlation among college students into cause and effect.\n\nThat's the habit this Quiz is trying to build: before you repeat a fun claim, ask what evidence it rests on and whether the claim stays within it.", + "references": "[Le et al. (2012)](https://arxiv.org/abs/1112.6209), [Stack Overflow Developer Survey 2016](https://survey.stackoverflow.co/2016/), [ScienceDaily: Myrick cat video study (2015)](https://www.sciencedaily.com/releases/2015/06/150616093357.htm), [Wikipedia: Deborah number](https://en.wikipedia.org/wiki/Deborah_number)" + } + ] +} diff --git a/content/quizzes/the-internet-runs-on-cats/cat-curled-in-sink.webp b/content/quizzes/the-internet-runs-on-cats/cat-curled-in-sink.webp new file mode 100644 index 0000000..7e001af Binary files /dev/null and b/content/quizzes/the-internet-runs-on-cats/cat-curled-in-sink.webp differ diff --git a/content/quizzes/the-internet-runs-on-cats/cat-detector-vs-baseline.svg b/content/quizzes/the-internet-runs-on-cats/cat-detector-vs-baseline.svg new file mode 100644 index 0000000..1742ffa --- /dev/null +++ b/content/quizzes/the-internet-runs-on-cats/cat-detector-vs-baseline.svg @@ -0,0 +1,67 @@ + + + + + Cat-face test: how much better than guessing? + Test mix: about 35% cat faces, 65% other images. Accuracy in percent. + Always answer "not a cat" + + 64.8% + Best simple linear filter + + 67.2% + Best cat neuron + + 74.8% + + + 0 + 50 + 100 + Dashed line: the 64.8% you get without learning anything. + diff --git a/content/quizzes/the-internet-runs-on-cats/deborah-number-scale.svg b/content/quizzes/the-internet-runs-on-cats/deborah-number-scale.svg new file mode 100644 index 0000000..bb35b3e --- /dev/null +++ b/content/quizzes/the-internet-runs-on-cats/deborah-number-scale.svg @@ -0,0 +1,84 @@ + + + + + Liquid or solid? It depends on how long you watch + De = relaxation time ÷ observation time (log scale) + + + De < 1: it flows (liquid) + De > 1: it keeps its shape (solid) + + + 0.001 + + 0.01 + + 0.1 + + 1 + + 10 + + 100 + + 1,000 + + 10,000 + + + + Cat napping in a sink for an hour + De ≈ 0.017 + + + Cat in a 1/100 s photo + De = 6,000 + Assumes a cat takes about 1 minute to settle into a container. Glaciers flow + over years. On slow-motion video, a popped water balloon briefly holds its shape. + diff --git a/content/quizzes/the-internet-runs-on-cats/kitten-in-box.webp b/content/quizzes/the-internet-runs-on-cats/kitten-in-box.webp new file mode 100644 index 0000000..4a19603 Binary files /dev/null and b/content/quizzes/the-internet-runs-on-cats/kitten-in-box.webp differ diff --git a/content/quizzes/the-internet-runs-on-cats/paw-vs-human-keypresses.svg b/content/quizzes/the-internet-runs-on-cats/paw-vs-human-keypresses.svg new file mode 100644 index 0000000..84b55bb --- /dev/null +++ b/content/quizzes/the-internet-runs-on-cats/paw-vs-human-keypresses.svg @@ -0,0 +1,176 @@ + + + + + How a paw on the keyboard differs from typing + The kind of signal PawSense reads, not its real algorithm + Person typing "cat" + + Q + + W + + E + + R + + 3 + + Y + + U + + I + + O + + P + + 2 + + S + + D + + F + + G + + H + + J + + K + + L + + Z + + X + + 1 + + V + + B + + N + + M + Paw landing on the keys + + Q + + W + + E + + R + + T + + Y + + U + + I + + O + + P + + A + + S + + • + + • + + • + + H + + J + + K + + L + + Z + + X + + • + + • + + • + + N + + M + One key at a time, spread across the + keyboard, at a human typing rhythm + A clump of neighboring keys pressed + at once, with no word-like rhythm + Key-down events over time + + + + + + + + + + + + time → + time → + diff --git a/content/quizzes/the-internet-runs-on-cats/stack-overflow-2016-dogs-vs-cats.svg b/content/quizzes/the-internet-runs-on-cats/stack-overflow-2016-dogs-vs-cats.svg new file mode 100644 index 0000000..d20f357 --- /dev/null +++ b/content/quizzes/the-internet-runs-on-cats/stack-overflow-2016-dogs-vs-cats.svg @@ -0,0 +1,74 @@ + + + + + Stack Overflow 2016 survey: "Dogs or cats?" + Developers who answered the question, in percent + All respondents (45,239) + Dogs + + 48.9% + Cats + + 34.8% + Other + + 16.4% + Germany (3,154) + Dogs + + 38.4% + Cats + + 44.5% + Other + + 17.0% + Only developers answered, so this can't tell you whether developers + like cats more or less than anyone else. + diff --git a/content/quizzes/the-internet-runs-on-cats/unlabeled-frames-to-cat-neuron.svg b/content/quizzes/the-internet-runs-on-cats/unlabeled-frames-to-cat-neuron.svg new file mode 100644 index 0000000..170ea37 --- /dev/null +++ b/content/quizzes/the-internet-runs-on-cats/unlabeled-frames-to-cat-neuron.svg @@ -0,0 +1,70 @@ + + + + + Google, 2012: a cat detector nobody asked for + + 10 million frames + one per YouTube video + 200 × 200 pixels + no labels at all + + 9-layer sparse + autoencoder + ~1 billion connections + 16,000 cores, 3 days + + one top-layer neuron + fires strongly for + cat faces + (others: faces, bodies) + + + Training goal: rebuild each image from a small, sparse code. + Cat faces are so common on YouTube that the network learned them as a feature. + diff --git a/content/quizzes/the-it-crowd-have-you-tried.json b/content/quizzes/the-it-crowd-have-you-tried.json new file mode 100644 index 0000000..be04355 --- /dev/null +++ b/content/quizzes/the-it-crowd-have-you-tried.json @@ -0,0 +1,207 @@ +{ + "schemaVersion": 1, + "id": "the-it-crowd-have-you-tried", + "title": "The IT Crowd: have you tried turning it off and on again?", + "description": "The IT Crowd ran for four series on Channel 4 and gave every help desk its motto. Start with the characters, the running jokes and the awards. Then find out why turning it off and on again actually fixes things, and why not every \"off\" counts.", + "language": "en", + "tags": ["internet-culture", "work", "debugging"], + "author": "Quizbun", + "questions": [ + { + "id": "roys-first-question", + "title": "Roy answers the IT department phone. What does he ask callers, episode after episode?", + "type": "single-choice", + "options": [ + { "text": "\"Have you tried turning it off and on again?\"", "isCorrect": true }, + { "text": "\"Have you tried clearing your cache?\"", "isCorrect": false }, + { "text": "\"What's your ticket number?\"", "isCorrect": false }, + { "text": "\"Have you read the manual?\"", "isCorrect": false } + ], + "explanation": "Roy first asks it in \"Yesterday's Jam\", the opening episode (2006), and keeps asking for the rest of the show. The joke lands because the advice is good. Real help desks start with a restart. It fixes a lot of problems for almost no effort, and it rules out the easy causes before anyone spends time on the hard ones.\n\nThe last two Questions explain why it works.", + "references": "[Wikipedia: The IT Crowd](https://en.wikipedia.org/wiki/The_IT_Crowd), [Wikipedia: List of The IT Crowd episodes](https://en.wikipedia.org/wiki/List_of_The_IT_Crowd_episodes)" + }, + { + "id": "new-emergency-number", + "title": "In \"Calamity Jen\", a TV advert sings the new emergency services number. Type the number.", + "description": "Type it as one block of digits or in the groups from the jingle.", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": [ + "0118 999 881 999 119 725 3", + "01189998819991197253", + "0118 999 881 999 119 7253", + "0118 999 881 999 119 725... 3", + "0118 999 881 999 119 725...3" + ] + }, + "explanation": "The number is **0118 999 881 999 119 725 3**: twenty digits, with a long pause before the final 3. Moss watches the advert and says it's easy to remember. Later in the same episode he sets the office on fire and reports it by email instead of calling anyone.\n\nReal emergency numbers are three digits long for a reason. Someone in a panic, maybe in the dark or in smoke, has to dial one without thinking. The \"0118\" also points to a real change: in 2003 the UK replaced its directory enquiries number, 192, with a crowd of commercial 118 numbers.", + "references": "[Wikipedia: List of The IT Crowd episodes](https://en.wikipedia.org/wiki/List_of_The_IT_Crowd_episodes)" + }, + { + "id": "real-uk-emergency-numbers", + "title": "Which of these numbers reach the police, fire and ambulance services from a UK phone?", + "type": "multiple-choice", + "options": [ + { "text": "999", "isCorrect": true }, + { "text": "112", "isCorrect": true }, + { "text": "111", "isCorrect": false }, + { "text": "101", "isCorrect": false }, + { "text": "118 118", "isCorrect": false } + ], + "explanation": "Both **999** and **112** reach the emergency services, and neither takes priority over the other. 112 is the emergency number across Europe. The UK added it in April 1995 on the same circuits as 999, so visitors who don't know the local number can still get through.\n\nThe other three are real numbers too, just for other things. 111 is the NHS line for urgent medical questions that aren't emergencies, 101 is the police non-emergency line, and 118 118 is one of the commercial directory enquiries services that the show's \"0118\" plays on.", + "references": "[Wikipedia: 999 (emergency telephone number)](https://en.wikipedia.org/wiki/999_(emergency_telephone_number)), [Wikipedia: 112 (emergency telephone number)](https://en.wikipedia.org/wiki/112_(emergency_telephone_number))" + }, + { + "id": "first-999-year", + "title": "999 is the oldest emergency telephone number in the world. In what year did it start in London?", + "description": "Enter a four-digit year. Answers within two years count.", + "type": "input", + "validation": { "mode": "numeric", "acceptedAnswers": [1937], "tolerance": 2 }, + "explanation": "999 started in the London area on **30 June 1937**. It was easy to dial in an emergency. Phones then had rotary dials, and 9 sits one hole away from the finger stop, so a caller in darkness or smoke could dial it by touch: put a finger in the second-to-last hole and turn the dial all the way, three times. Payphones of the time could also be cheaply modified to let the 9 through without a coin.\n\nThe IT Crowd's twenty-digit number would fail all of these tests.", + "references": "[Wikipedia: 999 (emergency telephone number)](https://en.wikipedia.org/wiki/999_(emergency_telephone_number))" + }, + { + "id": "company-name", + "title": "The IT department sits in the basement of which company?", + "type": "single-choice", + "options": [ + { "text": "Reynholm Industries", "isCorrect": true }, + { "text": "Wernham Hogg", "isCorrect": false }, + { "text": "Initech", "isCorrect": false }, + { "text": "Pied Piper", "isCorrect": false } + ], + "explanation": "The show is set at Reynholm Industries, 123 Carenden Road in central London. Its founder and chief executive is Denholm Reynholm (Chris Morris). His son Douglas (Matt Berry) later takes over.\n\nThe other three are fictional companies from other workplace comedies: Wernham Hogg from the British *The Office*, Initech from the film *Office Space*, and Pied Piper from *Silicon Valley*.", + "references": "[Wikipedia: The IT Crowd](https://en.wikipedia.org/wiki/The_IT_Crowd)" + }, + { + "id": "jen-job", + "title": "Jen Barber is hired to run the IT department. What does she know about computers?", + "type": "single-choice", + "options": [ + { + "text": "Almost nothing: her CV claimed a lot of experience with computers, and her interviewer knew even less", + "isCorrect": true + }, + { "text": "She is a former systems administrator promoted into management", "isCorrect": false }, + { "text": "She built Reynholm Industries' first network", "isCorrect": false }, + { "text": "She is a programmer who refuses to answer the phone", "isCorrect": false } + ], + "explanation": "Jen (Katherine Parkinson) says on her CV that she has \"a lot of experience with computers\". Denholm doesn't know enough to check, so she bluffs her way through the interview and becomes head of the department, which the show also calls relationship manager. In practice, her job is translating between the technicians and the rest of the building.\n\nReal support teams often have someone like that: a person who explains problems to non-technical staff and manages expectations, even if they can't fix a printer themselves.", + "references": "[Wikipedia: The IT Crowd](https://en.wikipedia.org/wiki/The_IT_Crowd)" + }, + { + "id": "richmond-server-room", + "title": "Richmond Avenal (Noel Fielding) is kept in the server room. Why was he sent there?", + "type": "single-choice", + "options": [ + { "text": "He became a goth", "isCorrect": true }, + { "text": "He crashed the company's email system", "isCorrect": false }, + { "text": "He is the only person who can repair the servers", "isCorrect": false }, + { "text": "He was hired as a security guard for the servers", "isCorrect": false } + ], + "explanation": "Richmond was once an up-and-coming executive at Reynholm Industries. When he became a goth, he was banished to the server room, out of sight of everyone else. It's a fitting spot for something a company would rather forget.\n\nReal server rooms are built for machines. Fans push air through the racks and cooling keeps the hardware within a safe temperature range, so the rooms are loud and cold for anyone working inside.", + "references": "[Wikipedia: The IT Crowd](https://en.wikipedia.org/wiki/The_IT_Crowd)" + }, + { + "id": "us-pilot", + "title": "In 2007 NBC filmed an American pilot of the show. Which member of the British cast played the same role in it?", + "type": "single-choice", + "options": [ + { "text": "Richard Ayoade", "isCorrect": true }, + { "text": "Chris O'Dowd", "isCorrect": false }, + { "text": "Katherine Parkinson", "isCorrect": false }, + { "text": "Chris Morris", "isCorrect": false } + ], + "explanation": "Richard Ayoade played Moss in both versions. The rest of the American cast was new: Joel McHale as Roy, Jessica St. Clair as Jen and Rocky Carroll as Denholm. NBC announced it as a series for the 2007 to 2008 season, but it never aired. The pilot leaked online in 2012.\n\nIn the British version, Chris O'Dowd played Roy, Katherine Parkinson played Jen and Chris Morris played Denholm.", + "references": "[Wikipedia: The IT Crowd](https://en.wikipedia.org/wiki/The_IT_Crowd)" + }, + { + "id": "friendface", + "title": "In the Series 3 episode \"Friendface\", Jen talks Roy and Moss into joining Friendface. What is it a parody of?", + "type": "single-choice", + "options": [ + { "text": "Social networking sites such as Facebook", "isCorrect": true }, + { "text": "Video-sharing sites such as YouTube", "isCorrect": false }, + { "text": "Search engines such as Google", "isCorrect": false }, + { "text": "Webmail services such as Hotmail", "isCorrect": false } + ], + "explanation": "Friendface is a social network, and the name is a thin disguise for Facebook. The episode aired on 19 December 2008. Facebook had opened to everyone only two years earlier, and joining a social network still felt new and a little embarrassing.\n\nThe episode hasn't dated much. Social networks still grow by pulling people you know, or used to know, back into your life, whether you want them there or not.", + "references": "[Wikipedia: List of The IT Crowd episodes](https://en.wikipedia.org/wiki/List_of_The_IT_Crowd_episodes)" + }, + { + "id": "awards-2008", + "title": "What did The IT Crowd win in 2008?", + "type": "single-choice", + "options": [ + { "text": "The International Emmy Award for comedy", "isCorrect": true }, + { "text": "The Primetime Emmy Award for Outstanding Comedy Series", "isCorrect": false }, + { "text": "The Golden Globe for Best Television Series, Musical or Comedy", "isCorrect": false }, + { "text": "Nothing; it won its first award after the final episode in 2013", "isCorrect": false } + ], + "explanation": "The show won the **International Emmy** for comedy in 2008. The International Emmys are separate awards for television made outside the United States, so a Channel 4 sitcom can win one. The Primetime Emmys are for American shows and don't cover it.\n\nBritish awards came next. The show won the BAFTA for Best Situation Comedy in 2009, and in 2014 Katherine Parkinson and Richard Ayoade won the BAFTAs for best female and male comedy performance.", + "references": "[Wikipedia: The IT Crowd](https://en.wikipedia.org/wiki/The_IT_Crowd), [Wikipedia: International Emmy Award for Best Comedy Series](https://en.wikipedia.org/wiki/International_Emmy_Award_for_Best_Comedy_Series)" + }, + { + "id": "episode-count", + "title": "Counting only the four regular series, how many episodes of The IT Crowd are there?", + "type": "input", + "validation": { "mode": "numeric", "acceptedAnswers": [24] }, + "explanation": "There are **24**: four series of six episodes each, from 2006 to 2010. Short series are normal for British sitcoms, which often have one or two writers. The IT Crowd had just one. An American network comedy of the same era usually ran for more than 20 episodes a season, written by a room of writers.\n\nA 48-minute special, \"The Internet Is Coming\", aired on 27 September 2013 and ended the show, bringing the total to 25.", + "references": "[Wikipedia: List of The IT Crowd episodes](https://en.wikipedia.org/wiki/List_of_The_IT_Crowd_episodes)" + }, + { + "id": "which-lines-are-real", + "title": "Which of these lines are from The IT Crowd?", + "type": "multiple-choice", + "options": [ + { "text": "\"Have you tried turning it off and on again?\"", "isCorrect": true }, + { "text": "\"Did you see that ludicrous display last night?\"", "isCorrect": true }, + { "text": "\"Dear Sir stroke Madam, I am writing to inform you of a fire…\"", "isCorrect": true }, + { "text": "\"Bazinga!\"", "isCorrect": false }, + { "text": "\"I'm not superstitious, but I am a little stitious.\"", "isCorrect": false } + ], + "explanation": "Three are real. Roy's catchphrase runs through the whole series. \"Did you see that ludicrous display last night?\" comes from \"Are We Not Men?\" (Series 3). Roy and Moss memorise football small talk from a website so they can pass as fans, and the reply they learn is a complaint that Arsenal's Wenger brought Walcott on too early. In \"Calamity Jen\", Moss starts his fire email with \"Dear Sir stroke Madam\", decides that's too formal, and changes it to \"Fire, exclamation mark\".\n\nThe other two come from American sitcoms. \"Bazinga!\" is Sheldon's catchphrase in *The Big Bang Theory*, and the \"stitious\" line is Michael Scott's in the American *The Office*.", + "references": "[Wikipedia: List of The IT Crowd episodes](https://en.wikipedia.org/wiki/List_of_The_IT_Crowd_episodes)" + }, + { + "id": "why-reboot-works", + "title": "Why does turning a computer off and on again fix so many problems?", + "type": "single-choice", + "options": [ + { + "text": "It throws away state that has built up: leaked memory, stuck processes, stale connections and caches", + "isCorrect": true + }, + { "text": "It installs any pending software updates", "isCorrect": false }, + { "text": "It cools the processor enough to stop calculation errors", "isCorrect": false }, + { "text": "It resets the hardware clock, which drifts over time", "isCorrect": false } + ], + "explanation": "A running computer keeps a lot of state in memory: open files, network connections, caches, locks and the internal flags of every program. Bugs damage that state a little at a time. A program with a **memory leak** forgets to give memory back, so it uses a bit more every hour. A process waiting for a lock that nobody releases waits forever. A network connection whose other end has gone away still looks open.\n\nA restart throws all of that away. Every program starts again from its initial state, which has been tested far more than day 40 of uptime ever was. The chart shows the catch: the bug is still in the code, so the problems build up again. Engineers call this **software aging**. Some systems restart on a schedule to stay ahead of it, a practice called software rejuvenation.\n\nSome restarts also install updates, but that isn't what unfreezes a program. A reboot doesn't cool the processor in any useful way, and it doesn't reset the real-time clock, which runs on a battery so it keeps time through a restart.", + "images": [ + { + "src": "reboot-clears-leaked-memory.svg", + "alt": "Line chart of memory in use over days of uptime. From a clean start, the line climbs steadily as leaked memory piles up. At each reboot it drops straight back to the starting level, then climbs again at the same rate. A dashed line near the top marks running out of memory, labelled slowdowns, freezes and crashes. After the last reboot the line is climbing towards it again, labelled bug still there, climbing again.", + "caption": "An illustration, not measured data. Chart: Quizbun.", + "placement": "explanation", + "width": 720, + "height": 420 + } + ], + "references": "[Wikipedia: Software aging](https://en.wikipedia.org/wiki/Software_aging), [Wikipedia: Memory leak](https://en.wikipedia.org/wiki/Memory_leak). For real systems that crashed after running too long, see the Quiz \"Have you tried turning it off and on again? Real bugs a reboot hides\"." + }, + { + "id": "windows-shut-down-vs-restart", + "title": "On a Windows 10 or 11 PC with default settings, which one starts the Windows kernel from scratch?", + "type": "single-choice", + "options": [ + { "text": "Restart", "isCorrect": true }, + { "text": "Shut down, then press the power button", "isCorrect": false }, + { "text": "Both give an equally fresh start", "isCorrect": false }, + { "text": "Neither; only unplugging the power cable does", "isCorrect": false } + ], + "explanation": "Since Windows 8, **Fast Startup** has been on by default. When you choose Shut down, Windows closes your apps and signs you out, but it keeps the kernel session. It saves the kernel and loaded drivers to the hibernation file, `hiberfil.sys`, and loads them back the next time you power on. Startup is faster, but any bad state in the kernel or a driver comes back too.\n\nMicrosoft's documentation says Fast Startup doesn't apply to Restart. Restart runs the full boot, because people restart when they want Windows completely fresh. So when the help desk says \"turn it off and on again\" on a modern Windows PC, Restart is the button that does it.\n\nIf you shut your PC down every night and it still feels sluggish after a few weeks, try Restart instead.", + "references": "[Microsoft Learn: Fast startup causes hibernation or shutdown to fail](https://learn.microsoft.com/en-us/troubleshoot/windows-client/setup-upgrade-and-drivers/fast-startup-causes-system-hibernation-shutdown-fail), [Microsoft Learn: Distinguishing fast startup from wake-from-hibernation](https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/distinguishing-fast-startup-from-wake-from-hibernation)" + } + ] +} diff --git a/content/quizzes/the-it-crowd-have-you-tried/reboot-clears-leaked-memory.svg b/content/quizzes/the-it-crowd-have-you-tried/reboot-clears-leaked-memory.svg new file mode 100644 index 0000000..825d980 --- /dev/null +++ b/content/quizzes/the-it-crowd-have-you-tried/reboot-clears-leaked-memory.svg @@ -0,0 +1,68 @@ + + + + + + Why a reboot fixes a slow computer, for a while + + + + + + Memory in use + Days of uptime + + + + Out of memory: slowdowns, freezes, crashes + + + + + + + + Reboot + Reboot + + + Clean start + Leaked memory + piles up + Bug still there, + climbing again + + diff --git a/content/quizzes/what-the-bundler-did.json b/content/quizzes/what-the-bundler-did.json new file mode 100644 index 0000000..62ee39c --- /dev/null +++ b/content/quizzes/what-the-bundler-did.json @@ -0,0 +1,787 @@ +{ + "schemaVersion": 1, + "id": "what-the-bundler-did", + "title": "What the bundler did: tree shaking, chunks and source maps", + "description": "You imported one function and shipped 70 KB. Every question starts from a module graph, a bundle treemap, or a build config and asks what the bundler kept, why it kept it, and which change would make the bundle smaller. Covers tree shaking, `sideEffects`, code splitting, content hashes, and source maps. Sizes in the diagrams are rounded and illustrative.", + "language": "en", + "tags": ["javascript", "frontend", "performance", "bundlers"], + "author": "Quizbun", + "questions": [ + { + "id": "lodash-commonjs-named-import", + "title": "You wrote `import { debounce } from 'lodash'` and use nothing else from lodash. Why does the whole library end up in the bundle?", + "images": [ + { + "src": "treemap-lodash-whole.svg", + "alt": "Bundle treemap of dist/main.js, 83 KB minified. A small blue rectangle labelled src/ is 12 KB. A much larger amber rectangle labelled node_modules/lodash/lodash.js is 71 KB and is a single module.", + "width": 720, + "height": 300 + }, + { + "src": "treemap-lodash-es-after.svg", + "alt": "Treemap of the same bundle after switching the import to lodash-es. src/ is still 12 KB, a thin 3 KB sliver holds lodash-es debounce.js and the helpers it imports, and a large grey area marks the roughly 290 other lodash functions that were removed. Total 15 KB.", + "placement": "explanation", + "width": 720, + "height": 300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The `lodash` package is one CommonJS file, and a bundler cannot tree-shake an object that `module.exports` assembles at runtime", + "isCorrect": true + }, + { + "text": "Named imports are tree-shaken only in development builds", + "isCorrect": false + }, + { + "text": "`debounce` calls every other lodash function internally", + "isCorrect": false + }, + { + "text": "Tree shaking removes unused code from your own source files, never from `node_modules`", + "isCorrect": false + } + ], + "explanation": "The `lodash` package's entry point is `lodash.js`: one CommonJS file that builds a large object and assigns it to `module.exports`. Tree shaking needs ES modules: `import` and `export` statements that a bundler can read without running any code. A CommonJS module has none, so the bundler keeps the whole file even though you asked for one function.\n\nTwo fixes work. `lodash-es` publishes every function as its own ES module, so `import { debounce } from 'lodash-es'` brings in `debounce.js` and the few helpers it imports. The path import `import debounce from 'lodash/debounce'` also works. It loads one small CommonJS file plus its helpers and skips the big one.\n\n`node_modules` is not the obstacle. Tree shaking works on any dependency that ships ES modules.", + "references": "[webpack: Tree Shaking](https://webpack.js.org/guides/tree-shaking/) · [npm: lodash-es](https://www.npmjs.com/package/lodash-es)" + }, + { + "id": "why-commonjs-resists-tree-shaking", + "title": "Which features of CommonJS prevent a bundler from proving that an export is unused?", + "type": "multiple-choice", + "options": [ + { + "text": "`require()` is an ordinary function call, so it can sit inside an `if` or take a computed path", + "isCorrect": true + }, + { + "text": "Exports can be added under computed names, as in `exports[name] = fn`", + "isCorrect": true + }, + { + "text": "`module.exports` can be reassigned or mutated at any point while the module runs", + "isCorrect": true + }, + { + "text": "CommonJS files cannot be minified", + "isCorrect": false + }, + { + "text": "CommonJS modules always run in strict mode, which blocks static analysis", + "isCorrect": false + } + ], + "explanation": "An ES module declares its imports and exports at the top level, with string-literal specifiers. A bundler can list every export and every consumer before running anything, then delete the exports nobody imports.\n\nCommonJS settles all of that at runtime. `require` is a function, so `if (legacy) require('./old')` or `require('./locale/' + lang)` can only be resolved by running the code. Exports are properties on an object, so `exports[name] = fn` or a later `module.exports = somethingElse` changes the module's public surface while it executes. So the bundler keeps the whole module. webpack 5 can tree-shake simple cases, such as `exports.format = …` read through `require('./x').format`, but any dynamic use makes it keep everything.\n\nMinifiers handle CommonJS fine. They shorten names and drop dead code inside functions, but they cannot prove another file never reads a property. Strict mode is unrelated: CommonJS modules are sloppy mode by default, and ES modules are always strict.", + "references": "[webpack 5 release notes: CommonJS Tree Shaking](https://webpack.js.org/blog/2020-10-10-webpack-5-release/#commonjs-tree-shaking) · [MDN: JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules)" + }, + { + "id": "namespace-import-escapes", + "title": "`utils.js` has 20 named exports and you write `import * as utils from './utils.js'`. Which uses of `utils` force the bundler to keep all 20?", + "type": "multiple-choice", + "options": [ + { + "text": "`utils.formatPrice(total)`", + "isCorrect": false + }, + { + "text": "`utils[methodName](value)`", + "isCorrect": true + }, + { + "text": "`registerHelpers(utils)`", + "isCorrect": true + }, + { + "text": "`Object.keys(utils).forEach(log)`", + "isCorrect": true + } + ], + "explanation": "A namespace import does not defeat tree shaking by itself. When every use is a static property access such as `utils.formatPrice`, webpack, Rollup and esbuild treat it like `import { formatPrice }` and drop the other 19 exports.\n\nThe problem starts when you use the namespace object as a value. `utils[methodName]` can read any export, depending on a string known only at runtime. Passing `utils` to a function, or enumerating it with `Object.keys`, hands the whole object to code the bundler does not follow. In each case every export might be read, so all 20 stay.", + "references": "[Rollup: Tree-shaking](https://rollupjs.org/introduction/#tree-shaking)" + }, + { + "id": "unused-class-methods", + "title": "`api.js` exports one class, `ApiClient`, with 40 methods. Your app creates a client and calls only `getUser()`. What does tree shaking remove from the class?", + "type": "single-choice", + "options": [ + { + "text": "Nothing: all 40 methods ship", + "isCorrect": true + }, + { + "text": "The 39 methods you never call", + "isCorrect": false + }, + { + "text": "The whole class, because the app uses a method and not the class itself", + "isCorrect": false + }, + { + "text": "The unused methods, but only if the class is annotated with `/*#__PURE__*/`", + "isCorrect": false + } + ], + "explanation": "Tree shaking works on module exports and top-level statements. `ApiClient` is one export and you use it, so it stays, body and all.\n\nTo remove a single method, the bundler would have to prove that no code ever reads it. Methods live on the prototype, and JavaScript can reach them in ways static analysis cannot rule out: `client[action]()`, copying the prototype into a mixin, or a framework calling a method by name. Bundlers and minifiers do not attempt it.\n\n`/*#__PURE__*/` marks a *call* as removable when its result goes unused; it says nothing about which methods of a class are needed. If a large client class dominates your bundle, turn its methods into standalone exported functions such as `getUser(client, id)`, which tree shaking can drop one at a time. The Firebase SDK made this change in its modular version for the same reason.", + "references": "[webpack: Tree Shaking](https://webpack.js.org/guides/tree-shaking/) · [Firebase: Upgrade from the namespaced API to the modular API](https://firebase.google.com/docs/web/modular-upgrade)" + }, + { + "id": "pure-annotation", + "title": "Nothing imports `store`, yet the call that creates it survives minification. Which change lets the minifier drop it?", + "description": "The app imports only `selectCart` from this file. `createStore` comes from a library whose code the bundler cannot prove free of side effects.\n\n```js\n// store.js\nimport { createStore } from 'tiny-store';\n\nexport const store = createStore(rootReducer);\n\nexport function selectCart(state) {\n return state.cart;\n}\n```", + "type": "single-choice", + "options": [ + { + "text": "Write `export const store = /*#__PURE__*/ createStore(rootReducer);`", + "isCorrect": true + }, + { + "text": "Change `const` to `let`", + "isCorrect": false + }, + { + "text": "Move the `store` line below `selectCart`", + "isCorrect": false + }, + { + "text": "Make `store` the default export", + "isCorrect": false + } + ], + "explanation": "A bundler drops an unused variable, but it keeps a function call whose effects it cannot see. `createStore(rootReducer)` might write to a global, register a listener, or log something. Deleting the call would delete that behavior, so the minifier keeps it even though nothing reads `store`.\n\n`/*#__PURE__*/` is a promise from the author: this call has no side effects, so remove it if its result is unused. Terser, esbuild, Rollup and webpack all honor it. The tools trust you. If the call *does* have a side effect you need, the annotation deletes it without warning.\n\nCompilers add the annotation too. Babel's React preset marks every `React.createElement` call it emits, so unused elements get removed.", + "references": "[Rollup: treeshake.annotations](https://rollupjs.org/configuration-options/#treeshake-annotations) · [webpack: Tree Shaking](https://webpack.js.org/guides/tree-shaking/)" + }, + { + "id": "barrel-without-side-effects-flag", + "title": "`App.js` imports only `Button` from the `ui` barrel, yet `chart-lib` and `date-lib` ship in the bundle. Which changes remove them?", + "description": "`ui/index.js` contains only `export * from './…'` lines. `Chart.js` and `DatePicker.js` define components and run nothing at the top level. `chart-lib` and `date-lib` do run setup code when they load.", + "images": [ + { + "src": "barrel-module-graph.svg", + "alt": "Module graph. App.js imports { Button } from ui/index.js, a barrel of export-star lines. The barrel links to Button.js at 2 KB, Chart.js at 3 KB, and DatePicker.js at 4 KB. Chart.js imports chart-lib at 180 KB and DatePicker.js imports date-lib at 40 KB. The ui package.json has no sideEffects field.", + "width": 720, + "height": 440 + }, + { + "src": "barrel-graph-side-effects-false.svg", + "alt": "The same graph with \"sideEffects\": false in the ui package.json. Button.js is highlighted green. Chart.js, DatePicker.js, chart-lib and date-lib are greyed out behind dashed edges and marked as skipped: never evaluated, never bundled.", + "placement": "explanation", + "width": 720, + "height": 440 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "Import the component file directly: `import { Button } from 'ui/Button.js'`", + "isCorrect": true + }, + { + "text": "Add `\"sideEffects\": false` to the `ui` package's `package.json`", + "isCorrect": true + }, + { + "text": "Replace `export * from './Chart.js'` with `export { Chart } from './Chart.js'`", + "isCorrect": false + }, + { + "text": "Switch to `import * as ui from 'ui'` and use `ui.Button`", + "isCorrect": false + } + ], + "explanation": "A re-export is an import too. To evaluate the barrel, the bundler must load `Chart.js`, and loading `Chart.js` means evaluating `chart-lib`. With no `sideEffects` field, the bundler has to assume that evaluating any module might do something observable, such as registering a plugin or patching a global. `chart-lib` really does run setup code, so skipping it could change behavior. It stays.\n\n`\"sideEffects\": false` tells the bundler that every module in `ui` is safe to skip when none of its exports are used. The bundler then follows the barrel only as far as `Button.js`. Importing `ui/Button.js` directly gets the same result with no flag at all, because the barrel never enters the graph.\n\nNamed re-exports and namespace imports only change the syntax. The barrel still links to `Chart.js`, and nothing tells the bundler that skipping it is safe.", + "references": "[webpack: Tree Shaking](https://webpack.js.org/guides/tree-shaking/)" + }, + { + "id": "side-effects-false-drops-css", + "title": "After you add `\"sideEffects\": false` to your app's `package.json`, the production build loses every component's styles. What went wrong?", + "description": "The project builds with webpack and `css-loader`. Components load their styles like this:\n\n```js\nimport './Button.css';\n\nexport function Button(props) {\n // …\n}\n```", + "type": "single-choice", + "options": [ + { + "text": "`import './Button.css'` imports no bindings, so under `\"sideEffects\": false` the bundler treats it as removable", + "isCorrect": true + }, + { + "text": "`\"sideEffects\": false` turns off `css-loader`", + "isCorrect": false + }, + { + "text": "webpack bundles CSS only in development mode", + "isCorrect": false + }, + { + "text": "The flag makes webpack inline CSS into JavaScript, where the minifier strips it", + "isCorrect": false + } + ], + "explanation": "`import './Button.css'` exists only for its side effect: adding a stylesheet. It imports no names, so no code ever \"uses\" it. With `\"sideEffects\": false` you told webpack that any file in the package can be skipped when its exports go unused, and this import qualifies.\n\nThe field also accepts a list of globs for the files that do have side effects:\n\n```json\n{ \"sideEffects\": [\"*.css\", \"./src/polyfills.js\"] }\n```\n\nThe same thing happens to any import you keep only for its effect, such as a polyfill or a module that registers a custom element.", + "references": "[webpack: Tree Shaking](https://webpack.js.org/guides/tree-shaking/)" + }, + { + "id": "barrel-file-dev-server-requests", + "title": "`vite build` produces a small bundle, but under `vite dev` the page makes about 1,400 requests before it renders. Why?", + "description": "`App.tsx` imports three icons from `src/icons/index.ts`, a barrel file in your own source that re-exports 1,400 icon components.", + "images": [ + { + "src": "dev-vs-build-requests.svg", + "alt": "Two pipelines side by side. Under vite dev, unbundled ESM: App.tsx, then icons/index.ts, then a stack of 1,400 icon modules, about 1,402 requests before first render. Under vite build, bundled and tree-shaken: one file, index-4be1.js, holding App and 3 icons, fetched in 1 request.", + "placement": "explanation", + "width": 720, + "height": 330 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The dev server serves each source module as its own file, and the browser must fetch every module the barrel re-exports before it can run the barrel", + "isCorrect": true + }, + { + "text": "Vite turns off HTTP caching in development, so each icon is requested again on every import", + "isCorrect": false + }, + { + "text": "In development, tree shaking runs in the browser and needs every file first", + "isCorrect": false + }, + { + "text": "Hot module replacement opens one request per module to watch for changes", + "isCorrect": false + } + ], + "explanation": "Vite's dev server does not bundle your source code. It transforms each file when the browser asks for it and lets the browser's native ES module loader follow the imports. The browser cannot run `icons/index.ts` until it has fetched and linked every module that file re-exports, and it does not know you want only three of them. So one barrel costs 1,400 requests.\n\nTree shaking happens only in `vite build`, which is why production looks fine. Dependencies in `node_modules` avoid the problem because Vite pre-bundles them into a few files. Vite does not pre-bundle your own source, so your barrels stay slow. Import each icon from its own file, such as `./icons/ArrowLeft`, and the dev server serves only what you use.", + "references": "[Vite: Performance, Avoid Barrel Files](https://vite.dev/guide/performance#avoid-barrel-files)" + }, + { + "id": "node-env-dead-branch", + "title": "What removes this development-only warning from a production build?", + "description": "```js\nif (process.env.NODE_ENV !== 'production') {\n warnDev('Each child in a list needs a unique key');\n}\n```", + "images": [ + { + "src": "define-then-minify.svg", + "alt": "Three code panels. Your source: if process.env.NODE_ENV is not 'production', call warnDev, then render. After define: the same code with process.env.NODE_ENV replaced by the literal 'production'. After minify: only render() remains.", + "placement": "explanation", + "width": 720, + "height": 360 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The bundler replaces `process.env.NODE_ENV` with the string `'production'`, then the minifier deletes the branch as unreachable", + "isCorrect": true + }, + { + "text": "Tree shaking removes `warnDev` because the module does not export it", + "isCorrect": false + }, + { + "text": "The browser skips the block because `process` does not exist there", + "isCorrect": false + }, + { + "text": "The bundled code reads `NODE_ENV` from the visitor's machine when the page loads", + "isCorrect": false + } + ], + "explanation": "Two build steps work together. First, a define step replaces text: webpack's `mode: 'production'` and Vite's build both replace `process.env.NODE_ENV` with the string literal `\"production\"`. The condition becomes `\"production\" !== \"production\"`.\n\nSecond, the minifier folds that constant expression to `false` and deletes the unreachable block. If nothing else calls `warnDev`, it becomes unused and disappears too.\n\nWithout the define step, the browser would not skip anything; it would throw `ReferenceError: process is not defined`, because `process` is a Node.js global. The substitution matches only that exact expression, so `const env = process.env; if (env.NODE_ENV …)` defeats it. React and many other libraries use this trick, so their development warnings add nothing to production bundles.", + "references": "[webpack: DefinePlugin](https://webpack.js.org/plugins/define-plugin/) · [webpack: Mode](https://webpack.js.org/configuration/mode/)" + }, + { + "id": "polyfills-follow-browserslist", + "title": "Your analytics show that every visitor runs a browser released in the last two years. Which change removes most of the polyfill block?", + "images": [ + { + "src": "treemap-core-js.svg", + "alt": "Treemap of dist/main.js, 300 KB minified: src/ at 96 KB, react-dom at 132 KB, and a red polyfills block from core-js at 72 KB. The .browserslistrc reads \"> 0.2%, not dead, ie 11\". @babel/preset-env uses useBuiltIns entry, and the entry file starts with import \"core-js/stable\".", + "width": 720, + "height": 330 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Narrow `.browserslistrc` to the browsers you actually support", + "isCorrect": true + }, + { + "text": "Rely on tree shaking to drop the polyfills no code calls", + "isCorrect": false + }, + { + "text": "Move `core-js` from `dependencies` to `devDependencies`", + "isCorrect": false + }, + { + "text": "Switch the minifier from Terser to esbuild", + "isCorrect": false + } + ], + "explanation": "With `useBuiltIns: \"entry\"`, `@babel/preset-env` replaces `import \"core-js/stable\"` with one import for each polyfill your target browsers need. The targets come from `.browserslistrc`, and `ie 11` needs nearly everything: `Promise`, `Symbol`, `Array.prototype.includes`, and hundreds more. Target recent browsers only and most of those imports vanish, because the browsers already implement the features.\n\nTree shaking cannot help. A polyfill works by patching globals such as `Array.prototype`, a side effect the bundler must keep. The `devDependencies` list is a hint for package managers; bundlers follow imports regardless of where a package is listed. A different minifier might shave a few percent, but it keeps every polyfill.", + "references": "[Babel: @babel/preset-env](https://babeljs.io/docs/babel-preset-env) · [Browserslist](https://github.com/browserslist/browserslist)" + }, + { + "id": "date-library-budget", + "title": "Each change below is applied on its own. Which ones bring this bundle under its 150 KB budget?", + "description": "The project builds with webpack.", + "images": [ + { + "src": "treemap-moment-budget.svg", + "alt": "Treemap of dist/main.js, 310 KB minified, against a 150 KB budget. src/ is 80 KB, moment core is 60 KB, and a red block, moment/locale/*, holds 170 KB of more than 130 locale files. The app formats dates in English only and imports moment with import moment from 'moment'.", + "width": 720, + "height": 330 + }, + { + "src": "treemap-budget-after.svg", + "alt": "Two resulting bundles. Ignoring moment's locales: src/ at 80 KB plus moment at 60 KB, 140 KB in total. Swapping to date-fns: src/ at 80 KB plus about 8 KB of date-fns functions, about 88 KB in total. Both are under the 150 KB budget.", + "placement": "explanation", + "width": 720, + "height": 320 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "Configure webpack's `IgnorePlugin` to skip `moment`'s `./locale` directory", + "isCorrect": true + }, + { + "text": "Replace `moment` with `date-fns` and import only the functions you call", + "isCorrect": true + }, + { + "text": "Change the import to `import * as moment from 'moment'`", + "isCorrect": false + }, + { + "text": "Move `moment` to `devDependencies`", + "isCorrect": false + }, + { + "text": "Add `\"sideEffects\": false` to your app's `package.json`", + "isCorrect": false + } + ], + "explanation": "The locales are most of the problem. Moment loads a locale through a `require` whose path is only known at runtime. webpack cannot tell which locale will be requested, so it bundles every file in `moment/locale`: more than 130 of them for an app that speaks English. `IgnorePlugin` tells webpack to skip that directory. Moment's built-in English still works, and the bundle drops to 140 KB.\n\n`date-fns` avoids the problem by design. Each function is its own ES module, so `import { format, addDays } from 'date-fns'` bundles those two functions and their helpers, and a locale ships only if you import it.\n\nThe other three changes do nothing here. A namespace import of a CommonJS package still pulls in the whole package. Bundlers ignore whether a package is listed under `dependencies` or `devDependencies`. Your app's `sideEffects` flag describes your own files, not moment's, and it cannot make a runtime `require` path analyzable.", + "references": "[webpack: IgnorePlugin](https://webpack.js.org/plugins/ignore-plugin/) · [Moment.js: Project status](https://momentjs.com/docs/#/-project-status/)" + }, + { + "id": "duplicate-dependency-versions", + "title": "Why does the bundle contain two copies of `date-fns`?", + "images": [ + { + "src": "treemap-duplicate-date-fns.svg", + "alt": "Treemap of dist/main.js: src/ at 70 KB, legacy-calendar at 18 KB, date-fns 3.6.0 at 22 KB, and date-fns 2.30.0 at 20 KB highlighted in red. date-fns 3.6.0 resolves to node_modules/date-fns. date-fns 2.30.0 resolves to node_modules/legacy-calendar/node_modules/date-fns.", + "width": 720, + "height": 330 + }, + { + "src": "dependency-tree-two-versions.svg", + "alt": "Dependency tree. your-app depends on date-fns ^3.6.0, installed at node_modules/date-fns, and on legacy-calendar, which wants date-fns ^2.30.0. Version 2.30.0 is nested under legacy-calendar. Each import resolves to the nearest node_modules, so the bundler follows both paths and bundles both copies.", + "placement": "explanation", + "width": 720, + "height": 360 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Your app requires `date-fns` 3 and `legacy-calendar` requires version 2, so both are installed and each import resolves to its own copy", + "isCorrect": true + }, + { + "text": "The bundler copies a shared dependency into every package that imports it", + "isCorrect": false + }, + { + "text": "Tree shaking could not merge the two copies because they export different functions", + "isCorrect": false + }, + { + "text": "One copy is the ES module build and the other is the CommonJS build of the same version", + "isCorrect": false + } + ], + "explanation": "`^3.6.0` and `^2.30.0` share no version, because a caret range never crosses a major version. The package manager satisfies both by installing 3.6.0 at the top level and nesting 2.30.0 under `legacy-calendar`. Bundlers resolve packages the way Node.js does, from the nearest `node_modules` walking up from the importing file, so the two imports land on two different files. To the bundler they are unrelated modules that happen to share a name.\n\nTo fix it, upgrade `legacy-calendar` to a release that accepts date-fns 3. If you have verified that the current release works with 3, you can force one version with npm `overrides` (Yarn calls it `resolutions`). `npm ls date-fns` shows which package asks for which version.\n\nA package can also end up twice when one import gets its ES module build and another gets its CommonJS build. That is the dual package hazard, and it shows the *same* version twice.", + "references": "[npm: package.json overrides](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#overrides) · [Node.js: Dual CommonJS/ES module packages](https://nodejs.org/api/packages.html#dual-commonjses-module-packages)" + }, + { + "id": "parsed-versus-gzip-size", + "title": "A bundle analyzer reports 1,200 KB stat, 400 KB parsed and 110 KB gzip for the same file. Which number best predicts how long a phone spends parsing and compiling it?", + "type": "single-choice", + "options": [ + { + "text": "400 KB parsed", + "isCorrect": true + }, + { + "text": "110 KB gzip", + "isCorrect": false + }, + { + "text": "1,200 KB stat", + "isCorrect": false + }, + { + "text": "None of them; parse time depends only on the number of modules", + "isCorrect": false + } + ], + "explanation": "The three numbers measure different stages. *Stat* is the input: the source files before minification. *Parsed* is the file the bundler actually wrote, minified. *Gzip* approximates what crosses the network.\n\nCompression only matters on the network. The browser decompresses the response and hands all 400 KB to the JavaScript engine, and parse and compile time grow with that size. Gzip size predicts download time; parsed size predicts CPU time, which hurts low-end phones most. A 110 KB download can still cost a slow phone noticeable main-thread time.\n\nStat size counts comments, whitespace and code that minification and tree shaking later removed, so it overstates what the browser sees.", + "references": "[webpack-bundle-analyzer: Size definitions](https://github.com/webpack/webpack-bundle-analyzer#size-definitions) · [V8: The cost of JavaScript in 2019](https://v8.dev/blog/cost-of-javascript-2019)" + }, + { + "id": "shared-chunk-for-lazy-routes", + "title": "Two lazy-loaded routes both import `chart-lib`. How many copies of it does a Rollup or Vite build emit, and where?", + "images": [ + { + "src": "lazy-routes-shared-graph.svg", + "alt": "Module graph. main.js dynamically imports Dashboard.js and Reports.js, drawn as dashed arrows. Dashboard.js and Reports.js both statically import chart-lib, 180 KB.", + "width": 720, + "height": 340 + }, + { + "src": "lazy-routes-chunks.svg", + "alt": "Emitted chunks. main-8d2e.js loads Dashboard-1c9a.js and Reports-f07b.js on demand. Both route chunks import chart-lib-5e31.js, a single 180 KB chunk emitted once. Whichever route loads first fetches the shared chunk; the other route finds it cached.", + "placement": "explanation", + "width": 720, + "height": 380 + } + ], + "type": "single-choice", + "options": [ + { + "text": "One, in its own shared chunk that either route can load", + "isCorrect": true + }, + { + "text": "Two, one inside each route's chunk", + "isCorrect": false + }, + { + "text": "One, inside the entry chunk, so it loads on every page", + "isCorrect": false + }, + { + "text": "One, inside whichever route chunk the bundler processes first", + "isCorrect": false + } + ], + "explanation": "Rollup assigns each module to a chunk according to which entry points can reach it, and it counts every dynamic import as an entry point. `chart-lib` is reachable from both routes and not from the static imports of `main.js`, so it gets a chunk of its own. Each route chunk imports that chunk by filename.\n\nEvery other option costs more. Copying it into both routes makes a user who visits both download 180 KB twice. Putting it in the entry charges every visitor, including those who never open a chart. Leaving it inside one route's chunk would force the other route to download that route's code as well.\n\nwebpack's `SplitChunksPlugin` reaches the same result for lazy chunks, subject to size thresholds. By default it splits out shared modules only when they are larger than about 20 KB.", + "references": "[Rollup: Code splitting](https://rollupjs.org/tutorial/#code-splitting) · [webpack: SplitChunksPlugin](https://webpack.js.org/plugins/split-chunks-plugin/)" + }, + { + "id": "entry-chunk-contents", + "title": "In a Vite production build of this graph, which modules end up in the entry chunk?", + "images": [ + { + "src": "entry-chunk-graph.svg", + "alt": "Module graph. main.js statically imports App.js and analytics.js. App.js statically imports Header.js and dynamically imports Settings.js, drawn dashed. Settings.js statically imports Header.js and forms.js.", + "width": 720, + "height": 420 + }, + { + "src": "entry-chunk-result.svg", + "alt": "Emitted chunks. The entry chunk main-2a7c.js contains main.js, App.js, analytics.js and Header.js. A separate lazy chunk, Settings-9f14.js, contains Settings.js and forms.js and imports Header.js from the entry chunk.", + "placement": "explanation", + "width": 720, + "height": 360 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "`App.js`", + "isCorrect": true + }, + { + "text": "`analytics.js`", + "isCorrect": true + }, + { + "text": "`Header.js`", + "isCorrect": true + }, + { + "text": "`Settings.js`", + "isCorrect": false + }, + { + "text": "`forms.js`", + "isCorrect": false + } + ], + "explanation": "The entry chunk holds everything reachable from `main.js` through static imports: `App.js`, `analytics.js`, and `Header.js`, which `App.js` imports statically. The dashed edge is a split point, so `Settings.js` and the module only it needs, `forms.js`, go into a lazy chunk.\n\n`Header.js` is the interesting case. `Settings.js` imports it too, but the Settings chunk can only load after the entry chunk has run, so `Header.js` is guaranteed to be in memory already. Vite's production bundler sees this and makes the lazy chunk import `Header` from the entry chunk. It creates neither a copy nor a separate shared chunk.", + "references": "[Rollup: Code splitting](https://rollupjs.org/tutorial/#code-splitting)" + }, + { + "id": "static-import-defeats-lazy-load", + "title": "You lazy-loaded the editor with `import('./editor.js')`, yet the analyzer still shows it inside the entry chunk. What is the most likely cause?", + "images": [ + { + "src": "treemap-editor-in-entry.svg", + "alt": "Treemap after lazy-loading the editor. The entry chunk index-51d0.js is 470 KB: src/ at 60 KB, and editor.js plus codemirror at 410 KB, highlighted red. A separate EditorPage chunk is only 4 KB. EditorPage.js runs const { Editor } = await import('./editor.js').", + "width": 720, + "height": 330 + }, + { + "src": "editor-static-edge.svg", + "alt": "Module graph. main.js statically imports Header.js and dynamically imports EditorPage.js. EditorPage.js dynamically imports editor.js, which pulls in codemirror. Header.js also statically imports { hasUnsavedDraft } from editor.js, and that solid red edge puts editor.js in the entry chunk.", + "placement": "explanation", + "width": 720, + "height": 340 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Another module in the entry's graph imports something from `editor.js` statically", + "isCorrect": true + }, + { + "text": "`import()` only splits code when its specifier is a template literal", + "isCorrect": false + }, + { + "text": "Vite merges any chunk smaller than 500 KB into the entry", + "isCorrect": false + }, + { + "text": "Production builds inline dynamic imports so they load faster", + "isCorrect": false + } + ], + "explanation": "A dynamic import creates a split point only if the module is not already reachable through static imports. Here `Header.js` runs `import { hasUnsavedDraft } from './editor.js'` to show an unsaved-changes dot. `Header.js` is in the entry chunk, so `editor.js`, and CodeMirror with it, must be there too. The `import()` then returns a module that is already loaded.\n\nVite prints a warning for this case, saying the module *is dynamically imported by* one file *but also statically imported by* another, so the *dynamic import will not move module into another chunk*. To fix it, move `hasUnsavedDraft` into a small module of its own, so the header no longer pulls in the editor.\n\nVite's 500 KB figure is `build.chunkSizeWarningLimit`, a warning threshold for large chunks. It never merges chunks.", + "references": "[MDN: import()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) · [Vite: build.chunkSizeWarningLimit](https://vite.dev/config/build-options#build-chunksizewarninglimit)" + }, + { + "id": "template-literal-import-globs", + "title": "The app only ever loads English and German, yet `dist/` contains a chunk for each of the 42 files in `locales/`. Why?", + "description": "```js\nexport async function loadMessages(lang) {\n const { default: messages } = await import(`./locales/${lang}.js`);\n return messages;\n}\n```", + "type": "single-choice", + "options": [ + { + "text": "The bundler cannot know `lang` at build time, so it treats the template as a file pattern and emits a chunk for every match", + "isCorrect": true + }, + { + "text": "Every `import()` call preloads all sibling files in the same directory", + "isCorrect": false + }, + { + "text": "Dynamic imports turn off tree shaking, so the bundler copies the whole directory", + "isCorrect": false + }, + { + "text": "The bundler ran `loadMessages` during the build with every possible language", + "isCorrect": false + } + ], + "explanation": "A bundler has to emit every file the browser might request at runtime. With ``import(`./locales/${lang}.js`)``, the fixed parts of the template, the `./locales/` directory and the `.js` extension, form a pattern. The bundler matches it against the file system and emits one chunk per match, since any of them could be requested. webpack calls this a context module; Vite handles it through its support for variables in dynamic imports.\n\nThat is usually what you want. Each visitor downloads only their own locale, and the other 40 files stay on the server unused. Keep the pattern narrow, though. ``import(`./${name}`)`` would match every file in the directory. A fully dynamic `import(path)` leaves nothing to match, so bundlers warn and the import usually fails at runtime.", + "references": "[webpack: Dependency Management](https://webpack.js.org/guides/dependency-management/) · [Rollup plugins: dynamic-import-vars](https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars)" + }, + { + "id": "count-emitted-chunks", + "title": "How many JavaScript chunks does a default Rollup build emit for this graph?", + "description": "Count every `.js` file in the output, including the entry. Answer with a number.", + "images": [ + { + "src": "chunk-count-graph.svg", + "alt": "Module graph. main.js statically imports a.js and dynamically imports Settings.js and Admin.js, drawn dashed. Settings.js and Admin.js both statically import shared.js. Admin.js also statically imports chart.js.", + "width": 720, + "height": 360 + }, + { + "src": "chunk-count-result.svg", + "alt": "Four emitted chunks. main holds main.js and a.js. Settings holds Settings.js. Admin holds Admin.js and chart.js. shared holds shared.js, because it has two dynamic importers and no static path from main.", + "placement": "explanation", + "width": 720, + "height": 360 + } + ], + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [4] + }, + "explanation": "Rollup groups modules by the set of entry points that can reach them, and every dynamic import counts as an entry point. Label each module with its set:\n\n- `main.js` and `a.js` are reached only from `main`: the entry chunk.\n- `Settings.js` is reached only from the Settings import: its own chunk.\n- `Admin.js` and `chart.js` are reached only from the Admin import: its own chunk.\n- `shared.js` is reached from both Settings and Admin: a chunk of its own.\n\nFour distinct sets make four chunks. Folding `shared.js` into either route would make the other route download that route's code. Copying it into both would ship it twice to anyone who opens both pages.", + "references": "[Rollup: Code splitting](https://rollupjs.org/tutorial/#code-splitting)" + }, + { + "id": "vendor-chunk-caching", + "title": "Splitting `node_modules` into a separate `vendor` chunk does not reduce the total number of bytes. Why do it?", + "images": [ + { + "src": "vendor-cache.svg", + "alt": "A returning visitor after Tuesday's deploy. Monday: app-71c0.js at 45 KB and vendor-b93e.js at 260 KB. Tuesday, after one line of app code changed: app-e408.js at 45 KB is downloaded, while vendor-b93e.js keeps its filename and comes from the HTTP cache.", + "placement": "explanation", + "width": 720, + "height": 300 + } + ], + "type": "single-choice", + "options": [ + { + "text": "Dependencies change less often than app code, so the vendor file keeps its hashed name across most deploys and returning visitors load it from cache", + "isCorrect": true + }, + { + "text": "Browsers parse dependency code faster when it arrives in a separate file", + "isCorrect": false + }, + { + "text": "Tree shaking works only when dependencies sit in their own chunk", + "isCorrect": false + }, + { + "text": "Two files download in parallel, which halves the total download time", + "isCorrect": false + } + ], + "explanation": "With content hashes in filenames, a file's name changes only when its bytes change. If app code and dependencies share one chunk, a one-line fix renames the whole 305 KB file, and every returning visitor downloads all of it again. Split them, and a typical deploy renames only the 45 KB app chunk. The 260 KB vendor file keeps its name, and browsers serve it from cache. Served with `Cache-Control: max-age=31536000, immutable`, it is not even revalidated.\n\nThe split pays off only while dependencies change less often than your code. Upgrade a dependency and the vendor hash changes too. Parallel downloads help a little, but the same bytes still cross the same connection, so nothing is halved.", + "references": "[webpack: Caching](https://webpack.js.org/guides/caching/) · [MDN: Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control)" + }, + { + "id": "content-hash-cascade", + "title": "You changed one line in `Settings.js`. After the deploy, both `Settings-a81b.js` and the entry `main-3f2a.js` have new hashes. Why did the entry change?", + "images": [ + { + "src": "hash-cascade.svg", + "alt": "Code comparison. Before the deploy, main-3f2a.js contains import('./Settings-a81b.js'). After the deploy, main-9c1d.js contains import('./Settings-77e0.js'), highlighted red. A chunk's hash comes from its bytes, and the entry's bytes include the lazy chunk's filename, so renaming the lazy chunk gives the entry a new hash too. webpack's runtimeChunk: 'single' moves the map of chunk filenames into a small file of its own.", + "placement": "explanation", + "width": 720, + "height": 340 + } + ], + "type": "single-choice", + "options": [ + { + "text": "The entry's code contains the lazy chunk's filename, so the new filename changed the entry's bytes", + "isCorrect": true + }, + { + "text": "Every build derives all hashes from the build timestamp", + "isCorrect": false + }, + { + "text": "The entry chunk embeds a copy of every lazy chunk's code", + "isCorrect": false + }, + { + "text": "Content hashes are computed over the whole `dist/` directory", + "isCorrect": false + } + ], + "explanation": "`import('./Settings.js')` in your source becomes `import('./Settings-a81b.js')` in the output. Editing `Settings.js` changes its bytes, so its hash changes, so its filename changes. The entry has that filename written inside it, so the entry's bytes change too, and it gets a new hash of its own.\n\nThe change travels only toward importers. A chunk gets a new hash when its own code changes or when a chunk it references is renamed. The cost is that the entry chunk, which every visitor needs, is invalidated by edits to any lazy route. In webpack, `optimization.runtimeChunk: 'single'` moves the map of chunk filenames into a small runtime file, so that file absorbs the churn instead of your entry code. Hashes are deterministic, so a chunk nobody touched keeps its name.", + "references": "[webpack: Caching](https://webpack.js.org/guides/caching/) · [Rollup: output.chunkFileNames](https://rollupjs.org/configuration-options/#output-chunkfilenames)" + }, + { + "id": "count-renamed-chunks", + "title": "In the same four-chunk build, you edit only `chart.js`. How many of the four chunk filenames change?", + "description": "Answer with a number.", + "images": [ + { + "src": "chunk-count-result.svg", + "alt": "Four emitted chunks. main holds main.js and a.js. Settings holds Settings.js. Admin holds Admin.js and chart.js. shared holds shared.js, because it has two dynamic importers and no static path from main. main dynamically imports Settings and Admin; Settings and Admin both import shared.", + "width": 720, + "height": 360 + }, + { + "src": "hash-cascade-count.svg", + "alt": "After editing chart.js, main changes from 3f2a to 9c1d and Admin changes from 5d60 to e2b4, both highlighted red. Settings-a81b.js and shared-0c77.js are unchanged, highlighted green. Admin holds chart.js, so Admin gets a new hash. main names Admin's file, so main gets one too.", + "placement": "explanation", + "width": 720, + "height": 360 + } + ], + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [2] + }, + "explanation": "Follow the references upward from the edit. `chart.js` lives in the Admin chunk, so Admin's bytes change and it gets a new filename. `main` contains `import('./Admin-….js')`, so `main` changes as well. That makes two.\n\n`shared` is imported *by* Admin but holds no reference to Admin, so it keeps its name. `Settings` references only `shared`, which did not change, so it keeps its name too. A returning visitor downloads the new `main`, plus the new `Admin` if they open the admin page. The other two chunks come from cache." + }, + { + "id": "source-map-sources-index", + "title": "A production error points to `app.min.js:1:1042`. Which original file contains the failing code?", + "description": "The error is `TypeError: price is undefined`. Your error tracker decoded the mapping segment for that position into absolute values:\n\n```text\n[generatedColumn, sourceIndex, originalLine, originalColumn]\n[1042, 1, 41, 8]\n```\n\nThe relevant part of `app.min.js.map`:\n\n```json\n{\n \"version\": 3,\n \"file\": \"app.min.js\",\n \"sources\": [\"src/main.ts\", \"src/cart.ts\", \"src/price.ts\"],\n \"names\": [\"price\", \"total\"]\n}\n```\n\nAnswer with the file name, for example `main.ts`.", + "images": [ + { + "src": "sourcemap-segment.svg", + "alt": "The error app.min.js:1:1042, TypeError: price is undefined. The decoded segment for generated column 1042 has four fields: generated column 1042, index into sources 1, original line 41, original column 8. An arrow leads from index 1 to sources[1], src/cart.ts, highlighted between src/main.ts at index 0 and src/price.ts at index 2. Indexes, lines and columns in a source map all count from 0, so line 41 here is line 42 in your editor.", + "placement": "explanation", + "width": 720, + "height": 380 + } + ], + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": ["cart.ts", "src/cart.ts", "./src/cart.ts"] + }, + "explanation": "The second field of a mapping segment is an index into `sources`, and source map indexes count from 0. Index 1 is the second entry, `src/cart.ts`. The error mentions `price`, so `price.ts` looks tempting. But the message names the undefined value. It says nothing about which file tried to use it.\n\nLines and columns are zero-based too, so original line 41 is line 42 in your editor. The real `mappings` string does not store these values plainly: it encodes each field as a Base64 VLQ offset from the previous segment, which is why you let a tool decode it.", + "references": "[ECMA-426: Source map format specification](https://tc39.es/ecma426/)" + }, + { + "id": "source-map-exposure", + "title": "Your production site serves `app.js.map`, including `sourcesContent`, next to `app.js`. What can any visitor read from it?", + "type": "single-choice", + "options": [ + { + "text": "The original source of every file in the bundle, including comments and file paths", + "isCorrect": true + }, + { + "text": "Only the minified code, with the original variable names restored", + "isCorrect": false + }, + { + "text": "Your server-side code and the contents of your `.env` file", + "isCorrect": false + }, + { + "text": "Nothing, because browsers download source maps only for signed-in developers", + "isCorrect": false + } + ], + "explanation": "`sourcesContent` embeds the full text of each original file in the map, so DevTools can display it without fetching anything else. Anyone who opens DevTools, or requests `app.js.map` directly, gets your TypeScript as you wrote it: comments, TODOs, internal names, and your directory layout in `sources`.\n\nThe leak has limits, though. The map covers only files that went into the bundle, so server code stays private. An environment variable appears only if the build inlined it into client code, and then it was already readable in `app.js`. Browsers fetch a map only when DevTools is open, but the file is public to anyone who asks for it.\n\nMany teams publish source maps on purpose. If you would rather not, you can keep readable stack traces without publishing the source, as the next question shows.", + "references": "[ECMA-426: Source map format specification](https://tc39.es/ecma426/)" + }, + { + "id": "private-source-maps", + "title": "You want readable stack traces in your error tracker without letting visitors read your source. Which setups achieve that?", + "images": [ + { + "src": "hidden-source-map-flow.svg", + "alt": "A build with sourcemap 'hidden' produces app-8d1f.js with no sourceMappingURL comment, deployed to the public CDN, and app-8d1f.js.map with sources and sourcesContent, uploaded privately to the error tracker. The map is deleted from dist before deploy; the tracker matches incoming stack frames to the uploaded map by release.", + "placement": "explanation", + "width": 720, + "height": 330 + } + ], + "type": "multiple-choice", + "options": [ + { + "text": "Build hidden source maps (`sourcemap: 'hidden'` in Vite, `devtool: 'hidden-source-map'` in webpack), upload them to the tracker, and leave them out of the deploy", + "isCorrect": true + }, + { + "text": "Build normal source maps, upload them to the tracker, then delete the `.map` files from `dist/` before deploying", + "isCorrect": true + }, + { + "text": "Use inline source maps so that no separate `.map` file exists", + "isCorrect": false + }, + { + "text": "Use webpack's `devtool: 'eval-source-map'` in production", + "isCorrect": false + } + ], + "explanation": "The error tracker needs the map; visitors do not. Both working setups deliver the map to the tracker by upload and keep it off the public server.\n\nA *hidden* source map is an ordinary map without the `//# sourceMappingURL=` comment at the end of the JavaScript, so browsers never look for it. Deleting normal maps after upload also works. The leftover comment makes DevTools request a file that returns 404, which is harmless noise.\n\nInline maps are fully public. They embed the whole map as a base64 data URL inside `app.js`, shipping your source to every visitor and bloating the file. `eval-source-map` is a development setting: it wraps each module in `eval()` with its map attached, which is slow and exposes the source the same way.", + "references": "[Vite: build.sourcemap](https://vite.dev/config/build-options#build-sourcemap) · [webpack: devtool](https://webpack.js.org/configuration/devtool/)" + } + ] +} diff --git a/content/quizzes/what-the-bundler-did/barrel-graph-side-effects-false.svg b/content/quizzes/what-the-bundler-did/barrel-graph-side-effects-false.svg new file mode 100644 index 0000000..9e95ed5 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/barrel-graph-side-effects-false.svg @@ -0,0 +1,111 @@ + + + + + Module graph: import { Button } from 'ui' + ui is a workspace package; its package.json has "sideEffects": false + + App.js + + ui/index.js + barrel: export * from … + + Button.js + 2 KB + + Chart.js + 3 KB + + DatePicker.js + 4 KB + + chart-lib + 180 KB + + date-lib + 40 KB + + + { Button } + + + + + + + + + + + Skipped: never evaluated, + never bundled + diff --git a/content/quizzes/what-the-bundler-did/barrel-module-graph.svg b/content/quizzes/what-the-bundler-did/barrel-module-graph.svg new file mode 100644 index 0000000..89119db --- /dev/null +++ b/content/quizzes/what-the-bundler-did/barrel-module-graph.svg @@ -0,0 +1,109 @@ + + + + + Module graph: import { Button } from 'ui' + ui is a workspace package; its package.json has no sideEffects field + + App.js + + ui/index.js + barrel: export * from … + + Button.js + 2 KB + + Chart.js + 3 KB + + DatePicker.js + 4 KB + + chart-lib + 180 KB + + date-lib + 40 KB + + + { Button } + + + + + + + + + + + diff --git a/content/quizzes/what-the-bundler-did/chunk-count-graph.svg b/content/quizzes/what-the-bundler-did/chunk-count-graph.svg new file mode 100644 index 0000000..a33f930 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/chunk-count-graph.svg @@ -0,0 +1,100 @@ + + + + + Module graph + solid arrow: static import · dashed arrow: import() + + main.js + + a.js + + Settings.js + + Admin.js + + shared.js + + chart.js + + + + + + + + + + + + + diff --git a/content/quizzes/what-the-bundler-did/chunk-count-result.svg b/content/quizzes/what-the-bundler-did/chunk-count-result.svg new file mode 100644 index 0000000..88c97a4 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/chunk-count-result.svg @@ -0,0 +1,88 @@ + + + + + Emitted chunks: 4 + + main-3f2a.js + main.js, a.js + + Settings-a81b.js + Settings.js + + Admin-5d60.js + Admin.js, chart.js + + shared-0c77.js + shared.js + shared.js has two dynamic importers and no static path from main, so it gets its own chunk. + diff --git a/content/quizzes/what-the-bundler-did/define-then-minify.svg b/content/quizzes/what-the-bundler-did/define-then-minify.svg new file mode 100644 index 0000000..1321d12 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/define-then-minify.svg @@ -0,0 +1,102 @@ + + + + + What removes the dev-only branch + + 1. your source + if ( + process.env.NODE_ENV + !== 'production' + ) { + warnDev(); + } + render(); + + 2. after define + if ( + 'production' + !== 'production' + ) { + warnDev(); + } + render(); + + 3. after minify + render(); + + + + + The define step pastes a string literal. The minifier folds 'production' !== 'production' to false, + then deletes the unreachable block, and warnDev with it if nothing else calls it. + diff --git a/content/quizzes/what-the-bundler-did/dependency-tree-two-versions.svg b/content/quizzes/what-the-bundler-did/dependency-tree-two-versions.svg new file mode 100644 index 0000000..28a2a09 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/dependency-tree-two-versions.svg @@ -0,0 +1,97 @@ + + + + + Two ranges that no single version satisfies + + your-app + package.json + + legacy-calendar + wants date-fns ^2.30.0 + + date-fns 3.6.0 + node_modules/date-fns + + date-fns 2.30.0 + nested under legacy-calendar + + + + + ^3.6.0 + + + Each import resolves to the nearest + node_modules. The bundler follows + both paths and bundles both copies. + diff --git a/content/quizzes/what-the-bundler-did/dev-vs-build-requests.svg b/content/quizzes/what-the-bundler-did/dev-vs-build-requests.svg new file mode 100644 index 0000000..86c4685 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/dev-vs-build-requests.svg @@ -0,0 +1,91 @@ + + + + + Same source, two pipelines + App.tsx imports 3 icons from src/icons/index.ts (1,400 re-exports) + vite dev: unbundled ESM + + App.tsx + + icons/index.ts + + + + 1,400 icon modules + ≈ 1,402 requests before first render + vite build: bundled + tree-shaken + + index-4be1.js + App + 3 icons + 1 request + diff --git a/content/quizzes/what-the-bundler-did/editor-static-edge.svg b/content/quizzes/what-the-bundler-did/editor-static-edge.svg new file mode 100644 index 0000000..cc4d5eb --- /dev/null +++ b/content/quizzes/what-the-bundler-did/editor-static-edge.svg @@ -0,0 +1,94 @@ + + + + + Module graph + solid arrow: static import · dashed arrow: import() + + main.js + + Header.js + + EditorPage.js + + editor.js + + codemirror + + + + + + + { hasUnsavedDraft } + + + diff --git a/content/quizzes/what-the-bundler-did/entry-chunk-graph.svg b/content/quizzes/what-the-bundler-did/entry-chunk-graph.svg new file mode 100644 index 0000000..db4fc1c --- /dev/null +++ b/content/quizzes/what-the-bundler-did/entry-chunk-graph.svg @@ -0,0 +1,100 @@ + + + + + Module graph + solid arrow: static import · dashed arrow: import() + + main.js + + App.js + + analytics.js + + Header.js + + Settings.js + + forms.js + + + + + + + + + + + + + diff --git a/content/quizzes/what-the-bundler-did/entry-chunk-result.svg b/content/quizzes/what-the-bundler-did/entry-chunk-result.svg new file mode 100644 index 0000000..dd6c87a --- /dev/null +++ b/content/quizzes/what-the-bundler-did/entry-chunk-result.svg @@ -0,0 +1,96 @@ + + + + + Emitted chunks + + main-2a7c.js (entry) + + main.js + + App.js + + analytics.js + + Header.js + + Settings-9f14.js + + Settings.js + + forms.js + + + imports Header.js + from the entry chunk + Header.js is already loaded with the entry, so the lazy chunk reuses it instead of copying it. + diff --git a/content/quizzes/what-the-bundler-did/hash-cascade-count.svg b/content/quizzes/what-the-bundler-did/hash-cascade-count.svg new file mode 100644 index 0000000..bdb335e --- /dev/null +++ b/content/quizzes/what-the-bundler-did/hash-cascade-count.svg @@ -0,0 +1,88 @@ + + + + + After editing chart.js + + main-3f2a → 9c1d.js + main.js, a.js + + Settings-a81b.js unchanged + Settings.js + + Admin-5d60 → e2b4.js + Admin.js, chart.js + + shared-0c77.js unchanged + shared.js + Admin holds chart.js, so Admin gets a new hash. main names Admin's file, so main gets one too. + diff --git a/content/quizzes/what-the-bundler-did/hash-cascade.svg b/content/quizzes/what-the-bundler-did/hash-cascade.svg new file mode 100644 index 0000000..c0428ef --- /dev/null +++ b/content/quizzes/what-the-bundler-did/hash-cascade.svg @@ -0,0 +1,84 @@ + + + + + Why the entry changed too + + // main-3f2a.js, before + const Settings = () => import('./Settings-a81b.js'); + + // main-9c1d.js, after the deploy + const Settings = () => import('./Settings-77e0.js'); + A chunk's hash comes from its bytes, and the entry's bytes include the lazy + chunk's filename. Rename the lazy chunk and the entry gets a new hash too. + webpack's runtimeChunk: 'single' moves the map of chunk filenames into a small file of its own. + diff --git a/content/quizzes/what-the-bundler-did/hidden-source-map-flow.svg b/content/quizzes/what-the-bundler-did/hidden-source-map-flow.svg new file mode 100644 index 0000000..4bf6440 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/hidden-source-map-flow.svg @@ -0,0 +1,100 @@ + + + + + Hidden source maps: readable stack traces, private source + + build + sourcemap: 'hidden' + + app-8d1f.js + no sourceMappingURL + + app-8d1f.js.map + sources + sourcesContent + + CDN + public + + error tracker + private upload + + + + + + + + + Upload the .map, then delete it from dist/ before deploy. Visitors get only minified code; + the tracker matches stack frames to the uploaded map by release. + diff --git a/content/quizzes/what-the-bundler-did/lazy-routes-chunks.svg b/content/quizzes/what-the-bundler-did/lazy-routes-chunks.svg new file mode 100644 index 0000000..8d86d3c --- /dev/null +++ b/content/quizzes/what-the-bundler-did/lazy-routes-chunks.svg @@ -0,0 +1,96 @@ + + + + + Emitted chunks + + main-8d2e.js + main.js + + Dashboard-1c9a.js + Dashboard.js + + Reports-f07b.js + Reports.js + + chart-lib-5e31.js + 180 KB, emitted once + + + + + + + + + Whichever route loads first fetches the shared chunk; the other route finds it cached. + diff --git a/content/quizzes/what-the-bundler-did/lazy-routes-shared-graph.svg b/content/quizzes/what-the-bundler-did/lazy-routes-shared-graph.svg new file mode 100644 index 0000000..90c94a7 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/lazy-routes-shared-graph.svg @@ -0,0 +1,93 @@ + + + + + Module graph + solid arrow: static import · dashed arrow: import() + + main.js + + Dashboard.js + + Reports.js + + chart-lib + 180 KB + + + + + + + + + diff --git a/content/quizzes/what-the-bundler-did/sourcemap-segment.svg b/content/quizzes/what-the-bundler-did/sourcemap-segment.svg new file mode 100644 index 0000000..64ab74c --- /dev/null +++ b/content/quizzes/what-the-bundler-did/sourcemap-segment.svg @@ -0,0 +1,103 @@ + + + + + Following one mapping segment + app.min.js:1:1042 TypeError: price is undefined + decoded segment for generated column 1042: + + 1042 + generated column + + 1 + index into sources + + 41 + original line + + 8 + original column + "sources": + + src/main.ts + sources[0] + + src/cart.ts + sources[1] + + src/price.ts + sources[2] + + + Indexes, lines and columns in a source map all count from 0. + Line 41 here is line 42 in your editor. + diff --git a/content/quizzes/what-the-bundler-did/treemap-budget-after.svg b/content/quizzes/what-the-bundler-did/treemap-budget-after.svg new file mode 100644 index 0000000..8264ebb --- /dev/null +++ b/content/quizzes/what-the-bundler-did/treemap-budget-after.svg @@ -0,0 +1,89 @@ + + + + + Two separate fixes, each under the 150 KB budget + Ignore moment's locales: 140 KB + + src/ + 80 KB + + moment + 60 KB + Swap to date-fns: ≈ 88 KB + + src/ + 80 KB + + date-fns ≈ 8 KB + only the functions you import + diff --git a/content/quizzes/what-the-bundler-did/treemap-core-js.svg b/content/quizzes/what-the-bundler-did/treemap-core-js.svg new file mode 100644 index 0000000..d493b99 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/treemap-core-js.svg @@ -0,0 +1,86 @@ + + + + + Bundle analyzer: dist/main.js + 300 KB minified · .browserslistrc: "> 0.2%, not dead, ie 11" + + src/ + 96 KB + + react-dom + 132 KB + + polyfills + core-js · 72 KB + @babel/preset-env with useBuiltIns: "entry"; entry file starts with import "core-js/stable" + diff --git a/content/quizzes/what-the-bundler-did/treemap-duplicate-date-fns.svg b/content/quizzes/what-the-bundler-did/treemap-duplicate-date-fns.svg new file mode 100644 index 0000000..d878156 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/treemap-duplicate-date-fns.svg @@ -0,0 +1,89 @@ + + + + + Bundle analyzer: dist/main.js + Why is date-fns here twice? + + src/ + 70 KB + + legacy-calendar 18 KB + + date-fns 3.6.0 + 22 KB + + date-fns 2.30.0 + 20 KB + date-fns 3.6.0 → node_modules/date-fns + date-fns 2.30.0 → node_modules/legacy-calendar/node_modules/date-fns + diff --git a/content/quizzes/what-the-bundler-did/treemap-editor-in-entry.svg b/content/quizzes/what-the-bundler-did/treemap-editor-in-entry.svg new file mode 100644 index 0000000..cc9b286 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/treemap-editor-in-entry.svg @@ -0,0 +1,86 @@ + + + + + Bundle analyzer: after lazy-loading the editor + EditorPage.js does: const { Editor } = await import('./editor.js') + + 60 KB + + editor.js + codemirror + 410 KB + + EditorPage + chunk + 4 KB + index-51d0.js (entry) · 470 KB + diff --git a/content/quizzes/what-the-bundler-did/treemap-lodash-es-after.svg b/content/quizzes/what-the-bundler-did/treemap-lodash-es-after.svg new file mode 100644 index 0000000..ae942e2 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/treemap-lodash-es-after.svg @@ -0,0 +1,84 @@ + + + + + After: import { debounce } from 'lodash-es' + 15 KB minified + + src/ + 12 KB + + + (removed) + the other ~290 lodash functions + ↑ lodash-es: debounce.js + the helpers it imports, 3 KB + diff --git a/content/quizzes/what-the-bundler-did/treemap-lodash-whole.svg b/content/quizzes/what-the-bundler-did/treemap-lodash-whole.svg new file mode 100644 index 0000000..1956547 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/treemap-lodash-whole.svg @@ -0,0 +1,86 @@ + + + + + Bundle analyzer: dist/main.js + 83 KB minified + + src/ + 12 KB + + node_modules/lodash/lodash.js + 71 KB · one module + + your code + + dependencies + diff --git a/content/quizzes/what-the-bundler-did/treemap-moment-budget.svg b/content/quizzes/what-the-bundler-did/treemap-moment-budget.svg new file mode 100644 index 0000000..db6053c --- /dev/null +++ b/content/quizzes/what-the-bundler-did/treemap-moment-budget.svg @@ -0,0 +1,86 @@ + + + + + Bundle analyzer: dist/main.js + 310 KB minified · budget: 150 KB + + src/ + 80 KB + + moment + 60 KB · core + + moment/locale/* + 170 KB · 130+ locale files + The app formats dates in English only. Source: import moment from 'moment' + diff --git a/content/quizzes/what-the-bundler-did/vendor-cache.svg b/content/quizzes/what-the-bundler-did/vendor-cache.svg new file mode 100644 index 0000000..ccd4b38 --- /dev/null +++ b/content/quizzes/what-the-bundler-did/vendor-cache.svg @@ -0,0 +1,90 @@ + + + + + A returning visitor after Tuesday's deploy + Monday + + app-71c0.js + 45 KB + + vendor-b93e.js + 260 KB + Tuesday (one line of app code changed) + + app-e408.js + 45 KB · downloaded + + vendor-b93e.js + 260 KB · from HTTP cache + Same filename, so the cached copy is still valid. Cache-Control: immutable skips revalidation. + diff --git a/content/quizzes/words-of-the-year-slang-decoder.json b/content/quizzes/words-of-the-year-slang-decoder.json new file mode 100644 index 0000000..fe2e810 --- /dev/null +++ b/content/quizzes/words-of-the-year-slang-decoder.json @@ -0,0 +1,296 @@ +{ + "schemaVersion": 1, + "id": "words-of-the-year-slang-decoder", + "title": "6-7, rizz and rage bait: decoding the words of the year", + "description": "Every year the big English dictionaries each pick a Word of the Year, and lately the winners come from TikTok and AI. This quiz covers the winners announced for 2023 to 2025: what they mean, where they came from and who picked them. The explanations also show how the dictionaries choose, mostly from search and usage data, with a public vote at Oxford.", + "language": "en", + "tags": ["languages", "everyday-life", "internet-culture"], + "author": "Quizbun", + "questions": [ + { + "id": "six-seven-meaning", + "title": "In 2025, a student answers \"How was the test?\" with \"6-7\" and a palms-up hand gesture. What do they mean?", + "type": "single-choice", + "options": [ + { + "text": "Something like \"so-so\" or \"maybe this, maybe that\", or nothing at all", + "isCorrect": true + }, + { "text": "They scored 6 or 7 out of 10", "isCorrect": false }, + { "text": "It's a countdown to the end of the lesson", "isCorrect": false }, + { "text": "It's code for \"I'll tell you later\"", "isCorrect": false } + ], + "explanation": "6-7 has no fixed meaning. The gesture, both palms up and bobbing up and down like a pair of scales, suggests \"so-so\" or \"could go either way\", and some people use it that way. Just as often it means nothing, and kids shout it whenever a 6 or a 7 comes up. Dictionary.com called it \"meaningless, ubiquitous, and nonsensical\", and Skrilla, whose song started it, has said he never gave it a meaning.\n\nThe phrase is still useful to the kids who say it. Knowing when to say it, and laughing when someone else does, shows you're part of the group. Asking what it means, as many adults do, shows you're not.", + "references": "[Dictionary.com: 2025 Word of the Year](https://www.dictionary.com/articles/word-of-the-year-2025), [Wikipedia: 6-7 (meme)](https://en.wikipedia.org/wiki/6-7_(meme))" + }, + { + "id": "six-seven-origin", + "title": "Where did \"6-7\" come from?", + "type": "single-choice", + "options": [ + { + "text": "A rap song, \"Doot Doot (6 7)\" by Skrilla", + "isCorrect": true + }, + { "text": "An episode of *South Park*", "isCorrect": false }, + { + "text": "A maths-class joke that 6 × 7 = 42, the \"answer to everything\"", + "isCorrect": false + }, + { "text": "A dance challenge started by a TikTok brand account", "isCorrect": false } + ], + "explanation": "It comes from the chorus of Skrilla's drill track \"Doot Doot (6 7)\", released in December 2024. Fans then set the song to basketball highlight clips, a March 2025 video of a boy yelling it at a game went viral, and searches for it climbed from June.\n\n*South Park* came later. Its season 28 premiere on 15 October 2025 built a plot around kids brainwashed by the meme, so the phrase was already famous enough to parody. The *Hitchhiker's Guide* joke is a coincidence. Nobody connected 6-7 to 42.", + "images": [ + { + "src": "six-seven-timeline.svg", + "alt": "Vertical timeline of how 6-7 went from a rap song to Word of the Year. December 2024: Skrilla releases the drill track Doot Doot (6 7). Next: fan edits pair it with LaMelo Ball, listed at 6 feet 7 inches. March 2025: a clip of a boy yelling it at a basketball game goes viral. Summer 2025: searches for 67 grow more than sixfold after June. 15 October 2025: South Park's season 28 premiere parodies it. 29 October 2025, highlighted: Dictionary.com names 67 its 2025 Word of the Year.", + "placement": "explanation", + "width": 720, + "height": 520 + } + ], + "references": "[Wikipedia: 6-7 (meme)](https://en.wikipedia.org/wiki/6-7_(meme))" + }, + { + "id": "six-seven-lamelo-ball", + "title": "Why is NBA player LaMelo Ball part of the 6-7 story?", + "type": "single-choice", + "options": [ + { "text": "He is listed at 6 feet 7 inches tall", "isCorrect": true }, + { "text": "He wears the number 67", "isCorrect": false }, + { "text": "He once scored 67 points in a game", "isCorrect": false }, + { "text": "He recorded the song with Skrilla", "isCorrect": false } + ], + "explanation": "LaMelo Ball is listed at 6 ft 7 in (2.01 m). Fans cut his highlights to the \"6 7\" line in Skrilla's song, and that height pun tied the number to basketball.\n\nThe number in the song had nothing to do with basketball. The fan edits gave it a second setting and a new audience: basketball fans.", + "references": "[Wikipedia: 6-7 (meme)](https://en.wikipedia.org/wiki/6-7_(meme)), [Wikipedia: LaMelo Ball](https://en.wikipedia.org/wiki/LaMelo_Ball)" + }, + { + "id": "six-seven-dictionary-data", + "title": "Dictionary.com named \"67\" its 2025 Word of the Year. What evidence showed its editors that the number itself was trending?", + "type": "single-choice", + "options": [ + { + "text": "Searches for 67 grew more than sixfold after June 2025, while most other two-digit numbers showed no trend", + "isCorrect": true + }, + { + "text": "A public vote in which 67 beat every other shortlisted word", + "isCorrect": false + }, + { + "text": "Teachers sent in more complaints about it than about any other word", + "isCorrect": false + }, + { + "text": "It appeared in more printed books in 2025 than any new word", + "isCorrect": false + } + ], + "explanation": "Dictionary.com's editors checked headlines, social media trends and search data. Searches for 67 rose more than sixfold after June 2025. The strongest evidence was the comparison with other two-digit numbers, most of which showed no real trend over the same months. On its own, a jump in searches for a number could come from homework, sports scores or noise. Next to the flat numbers, the jump clearly belonged to 67.\n\nThe public vote is Oxford's method. Printed books lag far behind slang, so Word of the Year teams rely on web and search data instead. Dictionary.com's 2025 shortlist also included *agentic*, *aura farming*, *clanker* and *tariff*. A number is an odd winner, so the announcement included a section on what counts as a word.", + "references": "[Dictionary.com: 2025 Word of the Year](https://www.dictionary.com/articles/word-of-the-year-2025), [CBS News: Dictionary.com picks 67](https://www.cbsnews.com/news/dictionary-67-2025-word-of-the-year-meaning/)" + }, + { + "id": "six-seven-school-bans", + "title": "In 2025, many schools told students to stop saying \"6-7\". Why?", + "type": "single-choice", + "options": [ + { + "text": "Students shouted it, with the gesture, whenever a 6 or a 7 came up, and it derailed lessons", + "isCorrect": true + }, + { "text": "It was a hidden code for sharing test answers", "isCorrect": false }, + { "text": "It has an offensive meaning", "isCorrect": false }, + { "text": "The song it comes from was banned from radio", "isCorrect": false } + ], + "explanation": "The bans were about disruption. Numbers come up all the time in school: page 67, question 6, a 6 on the board in maths. Each one set off a chorus of \"six-seven!\" with the hand gesture. Dictionary.com reported that teachers were soon swapping tips online on how to stop students saying it all day.\n\nNone of the other answers has any basis. The phrase has no hidden or offensive meaning. The joke is the interruption itself.", + "references": "[Dictionary.com: 2025 Word of the Year](https://www.dictionary.com/articles/word-of-the-year-2025), [Wikipedia: 6-7 (meme)](https://en.wikipedia.org/wiki/6-7_(meme))" + }, + { + "id": "rizz-origin", + "title": "Which longer word was shortened to give us \"rizz\"?", + "description": "Rizz means style, charm or attractiveness, especially the ability to attract a romantic partner.", + "type": "single-choice", + "options": [ + { "text": "Charisma", "isCorrect": true }, + { "text": "Rizzo, a character in *Grease*", "isCorrect": false }, + { "text": "Risqué", "isCorrect": false }, + { "text": "Rising", "isCorrect": false } + ], + "explanation": "Rizz comes from the middle of *cha-RIS-ma*. Linguists call this clipping: making a new word by cutting down an old one. Most clippings keep the front, as in *lab* from *laboratory*, or the back, as in *phone* from *telephone*. Keeping the middle is rarer. *Flu* from *influenza* is the best-known example, and rizz is another.\n\nThe double *z* works like the one in *jazz* and *fizz*. It keeps the vowel short, so the word rhymes with *fizz*, not *rise*.", + "references": "[Oxford University Press: Rizz crowned Oxford Word of the Year 2023](https://corp.oup.com/news/rizz-crowned-oxford-word-of-the-year-2023/), [Wikipedia: Clipping (morphology)](https://en.wikipedia.org/wiki/Clipping_(morphology))" + }, + { + "id": "rizz-year", + "title": "In which year did Oxford name \"rizz\" its Word of the Year?", + "description": "Type the four-digit year.", + "type": "input", + "validation": { "mode": "numeric", "acceptedAnswers": [2023] }, + "explanation": "Oxford picked rizz for 2023. More than 30,000 people voted in head-to-head rounds that cut eight shortlisted words down to four finalists: *rizz*, *Swiftie*, *situationship* and *prompt*. Oxford's lexicographers then chose the winner using the vote and their usage data.\n\n*Prompt*, the instruction you give an AI model, was already a finalist in 2023. Two years later, AI words won at three dictionaries.", + "references": "[Oxford University Press: Rizz crowned Oxford Word of the Year 2023](https://corp.oup.com/news/rizz-crowned-oxford-word-of-the-year-2023/)" + }, + { + "id": "brain-rot-thoreau", + "title": "Oxford's 2024 Word of the Year was first recorded in Henry David Thoreau's *Walden* (1854). Type the two-word term.", + "description": "Thoreau asked: \"While England endeavors to cure the potato-rot, will not any endeavor to cure the ______, which prevails so much more widely and fatally?\"", + "type": "input", + "validation": { + "mode": "text", + "acceptedAnswers": ["brain rot", "brain-rot", "brainrot"] + }, + "explanation": "It's *brain rot*. Thoreau used it to complain that his society valued simple ideas over complex ones. So the first recorded use of a word many people credit to TikTok is in a book from 1854.\n\nOxford defines brain rot as the supposed deterioration of a person's mental state from consuming too much trivial material, \"now particularly online content\". More than 37,000 people voted on the 2024 shortlist, and Oxford's data showed usage up 230% from 2023 to 2024. A lot of that use was self-mocking, with younger people joking about their own scrolling habits.", + "references": "[Oxford University Press: 'Brain rot' named Oxford Word of the Year 2024](https://corp.oup.com/news/brain-rot-named-oxford-word-of-the-year-2024/), [Wikipedia: Walden](https://en.wikipedia.org/wiki/Walden)" + }, + { + "id": "rage-bait", + "title": "A cooking video puts ketchup on carbonara, and the creator hopes angry comments will push it into more feeds. Which term, Oxford's 2025 Word of the Year, describes it?", + "type": "single-choice", + "options": [ + { "text": "Rage bait", "isCorrect": true }, + { "text": "Clickbait", "isCorrect": false }, + { "text": "Aura farming", "isCorrect": false }, + { "text": "Doomscrolling", "isCorrect": false } + ], + "explanation": "Oxford defines rage bait as online content deliberately designed to make people angry by being frustrating, provocative or offensive, usually to drive traffic or engagement. Feeds rank posts by engagement, and an angry comment counts as much as a happy one. All the creator needs is replies.\n\nClickbait is the closest wrong answer. It uses curiosity to get a click (\"You won't believe...\"), while rage bait uses outrage to get a reaction. *Aura farming*, one of the two words rage bait beat in the final vote, means building a cool image on purpose. More than 30,000 people voted, and Oxford's data showed usage of *rage bait* had tripled in 12 months.", + "references": "[Oxford University Press: Oxford Word of the Year 2025](https://corp.oup.com/word-of-the-year/)" + }, + { + "id": "parasocial", + "title": "Cambridge Dictionary's 2025 Word of the Year is *parasocial*. Which of these relationships is parasocial?", + "type": "single-choice", + "options": [ + { + "text": "A viewer who feels they know a streamer well, although the streamer has never heard of them", + "isCorrect": true + }, + { + "text": "Two pen pals who write every week but have never met", + "isCorrect": false + }, + { + "text": "Coworkers who only ever talk through a chat app", + "isCorrect": false + }, + { + "text": "Friends who met in an online game and play together every night", + "isCorrect": false + } + ], + "explanation": "A parasocial relationship is one-sided. One person feels a connection, and the other doesn't know they exist. The wrong answers all happen at a distance or through a screen, but in each one both people know each other and both reply, so none of them is parasocial.\n\nThe sociologists Donald Horton and Richard Wohl coined the term in 1956 for TV viewers who felt close to the people on screen. Cambridge picked it for 2025 because the idea has spread from celebrities and influencers to AI chatbots, which people now treat as friends. A chatbot is an odd case: it replies, but nobody on the other side knows you.", + "references": "[Cambridge University Press & Assessment: 'Parasocial' is Cambridge Dictionary's Word of the Year 2025](https://www.cambridge.org/news-and-insights/parasocial-is-cambridge-dictionary-word-of-the-year-2025), [Wikipedia: Parasocial interaction](https://en.wikipedia.org/wiki/Parasocial_interaction)" + }, + { + "id": "slop-meaning", + "title": "Merriam-Webster chose *slop* as its 2025 Word of the Year. Which meaning earned it the title?", + "type": "single-choice", + "options": [ + { + "text": "Low-quality digital content produced in quantity, usually by AI", + "isCorrect": true + }, + { "text": "Food scraps fed to pigs", "isCorrect": false }, + { "text": "Soft, wet mud", "isCorrect": false }, + { "text": "Overly sentimental writing", "isCorrect": false } + ], + "explanation": "Merriam-Webster's 2025 definition is \"digital content of low quality that is produced usually in quantity by means of artificial intelligence\". Think fake news images, junk AI-written books and \"workslop\" reports that waste coworkers' time.\n\nThe other answers are real meanings of *slop*, just older ones. It meant soft mud in the 1700s, food waste by the 1800s, and later anything of little value. The old senses stay in the dictionary while the new one takes over everyday use. Other dictionaries agreed that year: Australia's Macquarie Dictionary chose *AI slop*, and the American Dialect Society chose *slop*.", + "references": "[Merriam-Webster: Word of the Year 2025](https://www.merriam-webster.com/wordplay/word-of-the-year), [CBS News: \"Slop\" chosen as Merriam-Webster's 2025 word of the year](https://www.cbsnews.com/news/slop-merriam-webster-2025-word-of-the-year/)" + }, + { + "id": "vibe-coding-meaning", + "title": "Collins picked *vibe coding* as its Word of the Year for 2025. What does it describe?", + "type": "single-choice", + "options": [ + { + "text": "Building software by describing what you want in plain language and letting AI write the code", + "isCorrect": true + }, + { "text": "Programming while listening to music to stay in flow", "isCorrect": false }, + { + "text": "Writing code quickly without a plan, then fixing bugs as they appear", + "isCorrect": false + }, + { + "text": "Pair programming where one person talks and the other types", + "isCorrect": false + } + ], + "explanation": "Collins defines vibe coding as using AI, prompted by natural language, to help write computer code. You describe the app in plain words, the model writes it, and you judge the result by running it.\n\n\"Writing code without a plan\" sounds right, because \"vibes\" suggests improvising. But when you code without a plan, you still type every line yourself. When you vibe code, the AI writes the lines and you may never read them. Collins chose the term because its use grew fast after Andrej Karpathy coined it in a February 2025 post.", + "references": "[Collins: Word of the Year 2025](https://www.collinsdictionary.com/woty), [Collins Language Blog: Word of the Year 2025](https://blog.collinsdictionary.com/language-lovers/collins-word-of-the-year-2025-ai-meets-authenticity-as-society-shifts/)" + }, + { + "id": "vibe-coding-karpathy-post", + "title": "Andrej Karpathy coined *vibe coding* in a February 2025 post. What did the post describe?", + "type": "multiple-choice", + "options": [ + { "text": "Accepting every AI-suggested change without reading the diffs", "isCorrect": true }, + { "text": "Forgetting that the code even exists", "isCorrect": true }, + { "text": "Using it for throwaway weekend projects", "isCorrect": true }, + { "text": "Writing tests first so the AI's code can be trusted", "isCorrect": false }, + { "text": "Reviewing every line before it ships", "isCorrect": false } + ], + "explanation": "Karpathy, an OpenAI cofounder and Tesla's former AI director, wrote about giving in to the vibes and forgetting that the code even exists. He said he clicked \"Accept All\" and stopped reading the diffs, and that this was fine for throwaway weekend projects. Writing tests and reviewing every line are the opposite of what he described.\n\nThe weekend-project limit matters for developers. Simon Willison pointed out that if you review the code an AI writes, test it and can explain how it works, you aren't vibe coding any more. That's ordinary software development. Code nobody has read is fine in a weekend project. In a production system, someone will have to debug it and keep it secure.", + "references": "[Wikipedia: Vibe coding](https://en.wikipedia.org/wiki/Vibe_coding), [Simon Willison: Not all AI-assisted programming is vibe coding](https://simonwillison.net/2025/Mar/19/vibe-coding/)" + }, + { + "id": "which-won-in-2025", + "title": "Which of these won Word of the Year at a major English dictionary in 2025?", + "type": "multiple-choice", + "options": [ + { "text": "67", "isCorrect": true }, + { "text": "Vibe coding", "isCorrect": true }, + { "text": "Slop", "isCorrect": true }, + { "text": "Parasocial", "isCorrect": true }, + { "text": "Rage bait", "isCorrect": true }, + { "text": "Rizz", "isCorrect": false }, + { "text": "Brain rot", "isCorrect": false }, + { "text": "Hallucinate", "isCorrect": false } + ], + "explanation": "The 2025 winners were *67* (Dictionary.com), *vibe coding* (Collins), *slop* (Merriam-Webster), *parasocial* (Cambridge) and *rage bait* (Oxford). *Rizz* won at Oxford in 2023 and *brain rot* in 2024. *Hallucinate*, in the sense of an AI model confidently making things up, won at both Cambridge and Dictionary.com in 2023.\n\nAs the grid shows, the dictionaries rarely pick the same word, because they measure different things. Oxford combines a public vote with its usage data, Cambridge follows what people look up, Collins tracks its language corpus, and Dictionary.com weighs searches, headlines and social media. When different methods agree, as they did on *hallucinate* in 2023, the word was hard to miss.", + "images": [ + { + "src": "words-of-the-year-grid.svg", + "alt": "Grid of Words of the Year by dictionary and year. Oxford: rizz in 2023, brain rot in 2024, rage bait in 2025. Merriam-Webster: authentic, polarization, slop. Collins: AI, brat, vibe coding. Cambridge: hallucinate, manifest, parasocial. Dictionary.com: hallucinate, demure, 67. The 2025 column is highlighted.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ], + "references": "[Wikipedia: Word of the year](https://en.wikipedia.org/wiki/Word_of_the_year), [TIME: 2025's Words of the Year](https://time.com/7334730/word-of-the-year-2025-cambridge-collins-dictionary-oxford-merriam/)" + }, + { + "id": "ai-words-of-2025", + "title": "Which 2025 winners did their dictionaries link to AI when announcing them?", + "type": "multiple-choice", + "options": [ + { "text": "Vibe coding (Collins)", "isCorrect": true }, + { "text": "Slop (Merriam-Webster)", "isCorrect": true }, + { "text": "Parasocial (Cambridge)", "isCorrect": true }, + { "text": "Rage bait (Oxford)", "isCorrect": false }, + { "text": "67 (Dictionary.com)", "isCorrect": false } + ], + "explanation": "*Vibe coding* is about AI by definition, and Merriam-Webster's new sense of *slop* names AI-generated content outright. *Parasocial* is the surprise. The word dates from 1956 and television, but Cambridge said its 2025 surge came partly from people forming one-sided bonds with AI chatbots.\n\n*Rage bait* is about human creators and engagement algorithms, not generative AI, and *67* is pure playground. That leaves three of the five big winners tied to AI, and one of them, *vibe coding*, describes how software gets written.", + "references": "[Collins: Word of the Year 2025](https://www.collinsdictionary.com/woty), [Merriam-Webster: Word of the Year 2025](https://www.merriam-webster.com/wordplay/word-of-the-year), [Cambridge University Press & Assessment: 'Parasocial' is Cambridge Dictionary's Word of the Year 2025](https://www.cambridge.org/news-and-insights/parasocial-is-cambridge-dictionary-word-of-the-year-2025)" + }, + { + "id": "decode-the-group-chat", + "title": "Decode this group-chat message. Which definitions fit?", + "description": "> ngl the new intern has zero rizz, but they vibe coded the whole dashboard over the weekend and honestly it's not slop. still weird how parasocial they are about that one tech streamer tho", + "type": "multiple-choice", + "options": [ + { "text": "*rizz*: charm or social charisma", "isCorrect": true }, + { + "text": "*vibe coded*: had AI write the code from plain-language prompts", + "isCorrect": true + }, + { "text": "*slop*: low-quality output churned out by AI", "isCorrect": true }, + { + "text": "*parasocial*: attached to someone who doesn't know they exist", + "isCorrect": true + }, + { "text": "*rizz*: skill at writing code", "isCorrect": false }, + { "text": "*parasocial*: good at making friends online", "isCorrect": false } + ], + "explanation": "The sender says the intern is awkward (no rizz) but built the dashboard by prompting an AI (vibe coded), and the result is surprisingly good (not slop). The last line says the intern feels close to a streamer who has never met them (parasocial).\n\nBoth wrong answers come from guessing a word's meaning from its surroundings. In a message about a dashboard, \"coding skill\" looks like a fit for *rizz*, and the *social* in *parasocial* makes it sound friendly. When a message is about coding, it's easy to assume every word in it is about coding too.", + "references": "[Oxford University Press: Rizz crowned Oxford Word of the Year 2023](https://corp.oup.com/news/rizz-crowned-oxford-word-of-the-year-2023/), [Collins: Word of the Year 2025](https://www.collinsdictionary.com/woty)" + } + ] +} diff --git a/content/quizzes/words-of-the-year-slang-decoder/six-seven-timeline.svg b/content/quizzes/words-of-the-year-slang-decoder/six-seven-timeline.svg new file mode 100644 index 0000000..0a9d938 --- /dev/null +++ b/content/quizzes/words-of-the-year-slang-decoder/six-seven-timeline.svg @@ -0,0 +1,75 @@ + + + + + + How 6-7 went from a rap song to Word of the Year + + + + + + + Dec 2024 + + Skrilla releases the drill track "Doot Doot (6 7)" + + + Next + + Fan edits pair it with LaMelo Ball, listed at 6 ft 7 in + + + Mar 2025 + + A clip of a boy yelling it at a basketball game goes viral + + + Summer 2025 + + Searches for "67" grow more than sixfold after June + + + 15 Oct 2025 + + South Park's season 28 premiere parodies it + + + 29 Oct 2025 + + Dictionary.com names "67" its 2025 Word of the Year + + diff --git a/content/quizzes/words-of-the-year-slang-decoder/words-of-the-year-grid.svg b/content/quizzes/words-of-the-year-slang-decoder/words-of-the-year-grid.svg new file mode 100644 index 0000000..571b5bb --- /dev/null +++ b/content/quizzes/words-of-the-year-slang-decoder/words-of-the-year-grid.svg @@ -0,0 +1,87 @@ + + + + + + Words of the Year, 2023 to 2025 + + + + 2023 + 2024 + 2025 + + Oxford + + rizz + + brain rot + + rage bait + + Merriam-Webster + + authentic + + polarization + + slop + + Collins + + AI + + brat + + vibe coding + + Cambridge + + hallucinate + + manifest + + parasocial + + Dictionary.com + + hallucinate + + demure + + 67 + + diff --git a/content/quizzes/xkcd-predicted-it.json b/content/quizzes/xkcd-predicted-it.json new file mode 100644 index 0000000..c18fd87 --- /dev/null +++ b/content/quizzes/xkcd-predicted-it.json @@ -0,0 +1,442 @@ +{ + "schemaVersion": 1, + "id": "xkcd-predicted-it", + "title": "xkcd predicted it: comics that changed real software", + "description": "There's an xkcd for everything, and a few of them came true. NIST came around to the password comic. Flickr answered the \"is it a bird?\" joke within a month. And a boy named `Robert'); DROP TABLE Students;--` became the unofficial face of SQL injection. Each Question describes a comic, asks what happened next in the real world, then explains the idea behind it: password entropy, image recognition, SQL injection, Heartbleed, Hyrum's Law, and the fragile open-source projects everything depends on. The comics are described in words, and each one links to the original.", + "language": "en", + "tags": [ + "security", + "internet-culture" + ], + "author": "Quizbun", + "questions": [ + { + "id": "which-password-is-stronger", + "title": "Under the comic's assumptions, which password is harder to guess?", + "description": "xkcd #936, \"Password Strength\" (2011), compares two passwords. `Tr0ub4dor&3` is built the way many sign-up forms demanded. `correct horse battery staple` is four common words in a row. The comic assumes the attacker knows exactly how each one was made.", + "type": "single-choice", + "options": [ + { + "text": "`correct horse battery staple`", + "isCorrect": true + }, + { + "text": "`Tr0ub4dor&3`, because it mixes upper case, digits and a symbol", + "isCorrect": false + }, + { + "text": "About the same, because both are at least 11 characters long", + "isCorrect": false + }, + { + "text": "Neither, because any password made of dictionary words is cracked instantly", + "isCorrect": false + } + ], + "explanation": "A password is as strong as the number of guesses an attacker needs, and the comic counts those guesses for an attacker who knows the recipe.\n\n`Tr0ub4dor&3` looks random, but it's one uncommon word plus the tweaks everyone makes: capitalise the first letter, swap `o` for `0` and `a` for `4`, add a symbol and a digit. Cracking tools try exactly those tweaks. The comic puts the whole recipe at about 28 bits, roughly 2²⁸ or 268 million guesses.\n\nThe passphrase is all lowercase, but it's four words, each picked at random from a big list. Every random word multiplies the number of guesses by the size of the list. The next Question does the arithmetic.\n\n\"Dictionary words are cracked instantly\" is true for *one* word. With four random words, a dictionary attack has to try every combination, and that's what the comic is about.", + "references": "[xkcd #936: Password Strength](https://xkcd.com/936/), [explain xkcd: 936](https://www.explainxkcd.com/wiki/index.php/936)" + }, + { + "id": "passphrase-entropy-bits", + "title": "How many bits of entropy do four words picked at random from a list of 2,048 give?", + "description": "Each word is picked independently and at random, and the attacker knows the list. Answer in whole bits.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 44 + ] + }, + "explanation": "A 2,048-word list gives 2,048 equally likely choices per word, and 2,048 = 2¹¹, so each word is worth 11 bits.\n\nThe words are picked independently, so the possibilities multiply: 2¹¹ × 2¹¹ × 2¹¹ × 2¹¹ = 2⁴⁴. In bits, that's 11 + 11 + 11 + 11 = **44**. Bits add up when possibilities multiply, which is why entropy is measured in bits.\n\nA fifth word adds another 11 bits and makes the search 2,048 times longer. That does far more than swapping letters for look-alike digits.", + "references": "[xkcd #936: Password Strength](https://xkcd.com/936/), [explain xkcd: 936](https://www.explainxkcd.com/wiki/index.php/936), [Wikipedia: Password strength, entropy as a measure](https://en.wikipedia.org/wiki/Password_strength#Entropy_as_a_measure_of_password_strength)" + }, + { + "id": "passphrase-crack-time", + "title": "At 1,000 guesses per second, about how many years would it take to try all 2⁴⁴ passphrases?", + "description": "Use 2⁴⁴ ≈ 17.6 trillion and about 31.6 million seconds in a year. Anything within ten years counts.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 557 + ], + "tolerance": 10 + }, + "explanation": "17.6 trillion guesses at 1,000 per second is 17.6 billion seconds. Divide by 31.6 million seconds per year and you get about **557 years**. The comic rounds it to 550.\n\nOn average an attacker hits the right password halfway through, so expect about 280 years. That's still hopeless for the attacker. By the same arithmetic, `Tr0ub4dor&3` takes 2²⁸ guesses, or about 3 days.\n\nThe guess rate matters. 1,000 guesses per second is an attack on a live login form. An attacker who steals a database of fast, unsalted hashes can try billions per second, and then 44 bits falls in hours. Slow password hashes such as bcrypt and Argon2 exist to push that rate back down. Longer passphrases help too.", + "references": "[xkcd #936: Password Strength](https://xkcd.com/936/), [explain xkcd: 936](https://www.explainxkcd.com/wiki/index.php/936), [OWASP: Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)", + "images": [ + { + "src": "passphrase-entropy-bars.svg", + "alt": "Bar chart of password strength in bits. Tr0ub4dor&3, a common word with tweaks, reaches about 28 bits, about 3 days at 1,000 guesses per second. correct horse battery staple is four equal blocks of 11 bits each, 44 bits in total, about 550 years. A note says 16 more bits means 65,536 times as many guesses.", + "caption": "Numbers from xkcd #936. Chart drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 400 + } + ] + }, + { + "id": "what-makes-44-bits-real", + "title": "What has to be true for a four-word passphrase to really have 44 bits?", + "type": "single-choice", + "options": [ + { + "text": "Something random picks the words, such as dice or a password generator, not the person", + "isCorrect": true + }, + { + "text": "The attacker must not know the password is made of English words", + "isCorrect": false + }, + { + "text": "At least one word must be rare", + "isCorrect": false + }, + { + "text": "The words must be separated by spaces", + "isCorrect": false + } + ], + "explanation": "Entropy measures the process that made the password, not the string you end up with. 44 bits assumes all 2⁴⁴ combinations were equally likely.\n\nPeople are bad at random. Ask someone to \"pick four words\" and you get common nouns, song lyrics and phrases that make sense. An attacker who models that searches a much smaller space first. Diceware and password managers make the choice for you.\n\nThe comic's count already assumes the attacker knows the word list and the method, so keeping the method secret adds nothing. The security comes only from the random choice. Rare words don't help either. A rare word from a 2,048-word list is worth the same 11 bits as a common one. Spaces add nothing an attacker can't guess.\n\nThe comic's own example is now a bad password, because it's published everywhere and any wordlist can include it.", + "references": "[xkcd #936: Password Strength](https://xkcd.com/936/), [explain xkcd: 936](https://www.explainxkcd.com/wiki/index.php/936), [EFF: Dice-generated passphrases](https://www.eff.org/dice)" + }, + { + "id": "nist-2017-password-rules", + "title": "What did NIST's 2017 password guidelines, SP 800-63B, tell sites to do?", + "description": "The U.S. National Institute of Standards and Technology wrote the 2003 guidance behind many \"must contain a symbol, change every 90 days\" rules.", + "type": "multiple-choice", + "options": [ + { + "text": "Stop forcing users to change passwords periodically", + "isCorrect": true + }, + { + "text": "Stop imposing composition rules, such as requiring a mix of character types", + "isCorrect": true + }, + { + "text": "Check new passwords against lists of common and breached passwords", + "isCorrect": true + }, + { + "text": "Accept passwords of at least 64 characters, including spaces", + "isCorrect": true + }, + { + "text": "Require at least one special character", + "isCorrect": false + }, + { + "text": "Require a new password every 90 days", + "isCorrect": false + } + ], + "explanation": "SP 800-63B, published in June 2017, reversed the old advice. It says verifiers \"SHOULD NOT impose other composition rules\" and \"SHOULD NOT require memorized secrets to be changed arbitrarily (e.g., periodically)\". Sites should allow at least 64 characters, accept spaces, and reject passwords that appear on lists of breached, common or dictionary passwords. A forced change still makes sense when there's evidence a password was stolen.\n\nThe old rules failed because of how people react to them. Told to add a symbol, people write `Password1!`. Told to change it every 90 days, they write `Password2!`. A University of North Carolina study, summarised by the FTC's chief technologist in 2016, found that an attacker who knew a user's old password could guess the new one for 41% of accounts, in under 3 seconds per account.\n\nBill Burr, who wrote the 2003 guidance, told the *Wall Street Journal* in 2017: \"Much of what I did I now regret.\"", + "references": "[NIST SP 800-63B, §5.1.1.2 Memorized Secret Verifiers](https://pages.nist.gov/800-63-3/sp800-63b.html), [FTC: Time to rethink mandatory password changes](https://www.ftc.gov/policy/advocacy-research/tech-at-ftc/2016/03/time-rethink-mandatory-password-changes), [Engadget: The man who put us through password hell regrets everything](https://www.engadget.com/2017-08-08-nist-new-password-guidelines.html)" + }, + { + "id": "park-or-bird", + "title": "xkcd joked that spotting a bird in a photo needs a research team and five years. What happened next?", + "description": "xkcd #1425, \"Tasks\" (September 2014). A manager asks for an app that checks whether a photo was taken in a national park and whether it shows a bird. The developer says the first takes a few hours and the second needs \"a research team and five years\".", + "type": "single-choice", + "options": [ + { + "text": "Flickr released a working \"Park or Bird\" demo about a month later", + "isCorrect": true + }, + { + "text": "It took almost exactly five years, as the comic predicted", + "isCorrect": false + }, + { + "text": "Researchers showed the task can't be solved reliably from pixels alone", + "isCorrect": false + }, + { + "text": "Nothing public for years, until phones got built-in AI chips", + "isCorrect": false + } + ], + "explanation": "The comic ran on 24 September 2014. On 20 October, 26 days later, Flickr's engineering blog announced Park or Bird. You upload a photo, it checks the GPS location against park boundaries, and then a classifier decides whether there's a bird in it.\n\nFlickr didn't build the classifier in 26 days. It already had a deep neural network, trained on millions of images to recognise more than a thousand objects and scenes, and Park or Bird wrapped it. A few years earlier, the comic's estimate would have been reasonable. By 2014 the research had caught up.\n\nThe demo wasn't perfect, and Flickr called it a hard problem with room to improve. Spotting a bird well enough for a demo was solved in 2014. Spotting it every time still isn't.", + "references": "[xkcd #1425: Tasks](https://xkcd.com/1425/), [explain xkcd: 1425](https://www.explainxkcd.com/wiki/index.php/1425), [Flickr Code: Introducing Flickr PARK or BIRD](https://code.flickr.net/2014/10/20/introducing-flickr-park-or-bird/), [PetaPixel on Park or Bird](https://petapixel.com/2014/10/21/flickr-responds-webcomic-challence-site-tells-picture-bird/)" + }, + { + "id": "why-bird-became-easy", + "title": "Why was \"is it a bird?\" practical by 2014, when a few years earlier it was a research problem?", + "type": "single-choice", + "options": [ + { + "text": "From 2012, deep neural networks trained on GPUs with huge labelled image sets beat older methods by a wide margin", + "isCorrect": true + }, + { + "text": "Cameras began storing the subject of each photo in its metadata", + "isCorrect": false + }, + { + "text": "Programmers finally wrote enough rules by hand about beaks and wings", + "isCorrect": false + }, + { + "text": "Crowds of human workers labelled each uploaded photo behind the scenes", + "isCorrect": false + } + ], + "explanation": "The comic's two tasks have different inputs. \"Taken in a national park?\" needs two GPS numbers and a map lookup. \"Is it a bird?\" has only pixels, and a bird can be any colour, size, pose or angle. Rules written by hand can't cover that, which kept it a research problem for decades.\n\nThe change came in 2012. AlexNet, a deep convolutional network trained on GPUs with the 1.2-million-image ImageNet dataset, won that year's ImageNet competition with a top-5 error of 15.3%. The next-best entry had 26.2%. Nobody told the network what a beak is. It learned layers of features from labelled examples, first edges, then textures, then parts, then whole objects. Within two years, companies such as Flickr were running these networks in production.\n\nCameras record where and when a photo was taken, not what's in it. And paying people to label every upload doesn't scale for a photo-sharing site.", + "references": "[Flickr Code: Introducing Flickr PARK or BIRD](https://code.flickr.net/2014/10/20/introducing-flickr-park-or-bird/), [Wikipedia: AlexNet](https://en.wikipedia.org/wiki/AlexNet). Related Quiz: *The internet runs on cats*, for the 2012 network that learned \"cat\" without labels.", + "images": [ + { + "src": "park-or-bird-tasks.svg", + "alt": "Two cards compare the comic's tasks. Taken in a national park: the input is GPS coordinates in the photo's metadata, the method is checking whether the point is inside a park boundary, and the comic says a few hours. Is it a bird: the input is millions of pixel values, the method is learning what beaks, wings and feathers look like from millions of labelled examples, and the comic says a research team and five years. A timeline below shows xkcd #1425 on September 24 and Flickr's Park or Bird on October 20, 26 days later. A note says Park or Bird reused a deep network Flickr had already trained.", + "caption": "Dates from xkcd #1425 and the Flickr Code blog. Diagram drawn for this Quiz.", + "placement": "explanation", + "width": 720, + "height": 430 + } + ] + }, + { + "id": "bobby-tables-attack", + "title": "A school's database loses its student records after it enrolls a boy named `Robert'); DROP TABLE Students;--`. What kind of attack is this?", + "description": "xkcd #327, \"Exploits of a Mom\" (2007). His mother calls him Little Bobby Tables.", + "type": "single-choice", + "options": [ + { + "text": "SQL injection", + "isCorrect": true + }, + { + "text": "Cross-site scripting (XSS)", + "isCorrect": false + }, + { + "text": "A buffer overflow", + "isCorrect": false + }, + { + "text": "A denial-of-service attack", + "isCorrect": false + } + ], + "explanation": "SQL injection happens when a program builds an SQL query by pasting user input into the query text. The database can't tell which characters the programmer wrote and which came from the user, so any SQL syntax in the input runs as SQL.\n\nBobby's name is data, but the school's software ran it as code, and part of that code was `DROP TABLE Students`.\n\nCross-site scripting is the same mistake in HTML. Input pasted into a web page runs as JavaScript in someone's browser. A buffer overflow writes past the end of a fixed-size block of memory. Neither involves a database query. A denial-of-service attack floods a system with traffic, while deleting a table takes one precise command.\n\nThe comic became the unofficial mascot of this bug, and bobby-tables.com explains how to prevent it in dozens of languages.", + "references": "[xkcd #327: Exploits of a Mom](https://xkcd.com/327/), [explain xkcd: 327](https://www.explainxkcd.com/wiki/index.php/327), [OWASP: SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection), [bobby-tables.com](https://bobby-tables.com/)" + }, + { + "id": "bobby-tables-anatomy", + "title": "Which parts of the name make the attack work?", + "description": "Suppose the school's software builds its query like this, pasting the name between the quotes:\n\n```sql\nINSERT INTO Students (name) VALUES ('');\n```", + "type": "multiple-choice", + "options": [ + { + "text": "The `'` after `Robert`, which closes the string the program opened", + "isCorrect": true + }, + { + "text": "The `);` that finishes the `INSERT` statement early", + "isCorrect": true + }, + { + "text": "The `--` at the end, which turns the program's leftover `');` into a comment", + "isCorrect": true + }, + { + "text": "The first name `Robert`, which must match an existing student", + "isCorrect": false + }, + { + "text": "The capital letters in `DROP TABLE`, because SQL requires capitals for commands", + "isCorrect": false + } + ], + "explanation": "Paste the name into the template and the database receives:\n\n```sql\nINSERT INTO Students (name) VALUES ('Robert'); DROP TABLE Students;--');\n```\n\nNow read it the way the parser does. `'Robert'` is a complete string, because the apostrophe in the name closed it. `);` ends the `INSERT`. The parser is now outside any string, so it reads `DROP TABLE Students;` as a second statement. The program's own closing `');` is still there and would cause a syntax error, but `--` starts a comment, so the parser ignores it.\n\n`Robert` is only there to make the first statement a valid insert, and any name works. SQL keywords are case-insensitive, so `drop table` works just as well.\n\nThere's one more condition. The database driver has to allow several statements in one call. Some drivers refuse by default, which blocks this particular payload. That doesn't stop injection in general, because an attacker can still change what a single statement does, for example by adding `OR 1=1` to a `WHERE` clause.", + "references": "[xkcd #327: Exploits of a Mom](https://xkcd.com/327/), [explain xkcd: 327](https://www.explainxkcd.com/wiki/index.php/327), [OWASP: SQL Injection](https://owasp.org/www-community/attacks/SQL_Injection)", + "images": [ + { + "src": "bobby-tables-anatomy.svg", + "alt": "Diagram of the injection. The program's template is INSERT INTO Students (name) VALUES ('…name…');. With the name pasted in, the database receives INSERT INTO Students (name) VALUES ('Robert'); DROP TABLE Students;--'); with the injected parts highlighted. Three callouts explain them. '); closes the string and finishes the INSERT. DROP TABLE Students; is a second statement that deletes the whole table. -- comments out the template's leftover '); so nothing errors. A note says the fix is to send the SQL and the name separately.", + "caption": "Drawn for this Quiz from the name in xkcd #327.", + "placement": "explanation", + "width": 720, + "height": 440 + } + ] + }, + { + "id": "bobby-tables-real-fix", + "title": "Bobby's mother tells the school to sanitize its database inputs. What does OWASP recommend as the main defence?", + "type": "single-choice", + "options": [ + { + "text": "Parameterised queries, which send the SQL and the values to the database separately", + "isCorrect": true + }, + { + "text": "Escaping or removing quotes and semicolons from every input", + "isCorrect": false + }, + { + "text": "Rejecting names that contain apostrophes", + "isCorrect": false + }, + { + "text": "Hiding database error messages from users", + "isCorrect": false + } + ], + "explanation": "A parameterised query, also called a prepared statement, sends the query with a placeholder, then sends the name as a separate value:\n\n```py\ncursor.execute(\"INSERT INTO Students (name) VALUES (%s)\", (name,))\n```\n\nThe database parses the SQL before it sees the name, so the name can't change the query's structure. `Robert'); DROP TABLE Students;--` gets stored as an odd but harmless name.\n\nThe mother's advice is the weaker fix. OWASP's SQL Injection Prevention Cheat Sheet lists escaping user input last and calls it \"strongly discouraged\" as a primary defence, because escaping rules differ between databases and one missed case reopens the hole. Rejecting apostrophes breaks real names such as O'Brien and still leaves other syntax to exploit. Hiding error messages slows the attacker down but doesn't stop them.", + "references": "[OWASP: SQL Injection Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html), [bobby-tables.com](https://bobby-tables.com/)" + }, + { + "id": "heartbleed-hat-500", + "title": "What was the actual bug behind Heartbleed?", + "description": "xkcd #1354, \"Heartbleed Explanation\" (April 2014). A user asks a server to reply with \"POTATO\" (6 letters), and it does. Then she asks for \"HAT\" (500 letters), and the server sends back HAT followed by 497 characters of other users' data.", + "type": "single-choice", + "options": [ + { + "text": "OpenSSL trusted the length the client claimed and copied that many bytes, reading past the real data into nearby memory", + "isCorrect": true + }, + { + "text": "OpenSSL used encryption keys that were too short and could be brute-forced", + "isCorrect": false + }, + { + "text": "The heartbeat messages were sent unencrypted, so anyone on the network could read them", + "isCorrect": false + }, + { + "text": "Attackers flooded servers with heartbeats until they crashed", + "isCorrect": false + } + ], + "explanation": "TLS has a heartbeat feature. One side sends a payload along with its length, and the other side echoes the payload back to show the connection is alive. Vulnerable OpenSSL versions (1.0.1 through 1.0.1f) never checked the claimed length against the payload that actually arrived. Claim 64 KB, send 3 bytes, and the server copies 64 KB of memory starting at those 3 bytes.\n\nThe extra bytes were whatever sat next to them in the server's memory: session cookies, passwords, and sometimes the server's private key. Each request leaked up to 64 KB, and an attacker could just keep asking. The fix, in OpenSSL 1.0.1g, was a bounds check.\n\nThis comic didn't predict anything. The bug was disclosed on 7 April 2014 as CVE-2014-0160, and the comic ran four days later. It became one of the most-shared explanations because \"HAT (500 letters)\" sums up the bug in one line.", + "references": "[xkcd #1354: Heartbleed Explanation](https://xkcd.com/1354/), [explain xkcd: 1354](https://www.explainxkcd.com/wiki/index.php/1354), [heartbleed.com](https://heartbleed.com/)" + }, + { + "id": "spacebar-heating-law", + "title": "An update fixes a bug that made the CPU overheat when the spacebar was held down, and a user complains that it broke their workflow. Which named principle is this?", + "description": "xkcd #1172, \"Workflow\" (2013). The user had set up their editor to treat the temperature spikes as a Control key press.", + "type": "single-choice", + "options": [ + { + "text": "Hyrum's Law", + "isCorrect": true + }, + { + "text": "Conway's Law", + "isCorrect": false + }, + { + "text": "Murphy's Law", + "isCorrect": false + }, + { + "text": "Moore's Law", + "isCorrect": false + } + ], + "explanation": "Hyrum's Law is named after Hyrum Wright, who noticed it while working on Google's core libraries. It says: \"With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.\"\n\nThe comic's example is absurd on purpose, but real cases are mundane. Callers depend on the exact text of an error message, on the order of results a function never promised to sort, or on how long an operation takes. Change any of these and something downstream breaks, even though the documented behaviour stayed the same.\n\nSome language designers push back on purpose. Go's spec leaves map iteration order unspecified, and since Go 1 the runtime varies the order between runs so nobody can come to rely on it.\n\nConway's Law says systems mirror the communication structure of the organisations that build them. Murphy's Law is the general \"anything that can go wrong will\". Moore's Law is about transistor counts doubling.", + "references": "[xkcd #1172: Workflow](https://xkcd.com/1172/), [explain xkcd: 1172](https://www.explainxkcd.com/wiki/index.php/1172), [Hyrum's Law](https://www.hyrumslaw.com/), [Go 1 release notes: map iteration](https://go.dev/doc/go1#iteration)" + }, + { + "id": "is-it-worth-the-time", + "title": "You can shave 5 minutes off a task you do every day. How many days can you spend automating it before you lose time over five years?", + "description": "xkcd #1205, \"Is It Worth the Time?\", answers this for many cases, with five years as the horizon. Count full 24-hour days and round to a whole number.", + "type": "input", + "validation": { + "mode": "numeric", + "acceptedAnswers": [ + 6 + ], + "tolerance": 0.5 + }, + "explanation": "5 minutes × 365 days × 5 years = 9,125 minutes. That's about 152 hours, or **6.3 days** around the clock. The comic's table says 6 days.\n\nThose are 24-hour days. In 8-hour working days it's about 19, which is more than most people guess for a five-minute saving. The table works as a sanity check both ways. Shaving 5 seconds off a daily task is worth only about 2 hours of effort, and shaving 1 hour off a daily task is worth about 2 months.\n\nThe table leaves a few things out. Automation needs maintenance, and it can be worth more than the time it saves when it prevents mistakes or lets someone else do the task. xkcd #1319, \"Automation\", jokes about the other risk, which is that the automation itself becomes the task.", + "references": "[xkcd #1205: Is It Worth the Time?](https://xkcd.com/1205/), [explain xkcd: 1205](https://www.explainxkcd.com/wiki/index.php/1205), [xkcd #1319: Automation](https://xkcd.com/1319/), [explain xkcd: 1319](https://www.explainxkcd.com/wiki/index.php/1319)" + }, + { + "id": "standards-charger-ending", + "title": "xkcd's \"Standards\" comic uses phone chargers as an example. What finally cut down the charger mess in the EU?", + "description": "xkcd #927, \"Standards\" (2011). There are 14 competing standards. Someone proposes one universal standard that covers everyone's use cases. Soon there are 15 competing standards.", + "type": "single-choice", + "options": [ + { + "text": "A law requiring USB-C charging ports on phones and other small devices, from the end of 2024", + "isCorrect": true + }, + { + "text": "Manufacturers agreed on USB-C on their own because it was the better port", + "isCorrect": false + }, + { + "text": "Wireless charging made charging ports obsolete", + "isCorrect": false + }, + { + "text": "Apple licensed its Lightning connector to other manufacturers", + "isCorrect": false + } + ], + "explanation": "Directive (EU) 2022/2380 amended the EU's Radio Equipment Directive. Since 28 December 2024, phones, tablets, cameras, headphones, e-readers and other small devices sold in the EU must charge over USB-C. Laptops followed on 28 April 2026. Fast charging above 15 W must use USB Power Delivery.\n\nThe comic is about incentives. A new \"universal\" standard is just one more option unless everyone has a reason to drop their own. USB-C had been capable for years, and the voluntary 2009 agreement on micro-USB left gaps. The EU took the choice away by law.\n\nWireless charging has competing standards of its own and doesn't replace a port. Lightning stayed Apple's own connector until the law pushed iPhones to USB-C.", + "references": "[xkcd #927: Standards](https://xkcd.com/927/), [explain xkcd: 927](https://www.explainxkcd.com/wiki/index.php/927), [European Commission: The EU common charger](https://single-market-economy.ec.europa.eu/sectors/electrical-and-electronic-engineering-industries-eei/radio-equipment-directive-red/one-common-charging-solution-all_en)" + }, + { + "id": "xz-backdoor-discovery", + "title": "How was the 2024 xz Utils backdoor discovered?", + "description": "xkcd #2347, \"Dependency\" (2020), draws all modern digital infrastructure as a tower of blocks balanced on one small block, \"a project some random person in Nebraska has been thanklessly maintaining since 2003\". xz Utils, a compression library used by many Linux systems, was that kind of project.", + "type": "single-choice", + "options": [ + { + "text": "An engineer noticed SSH logins on his test machine taking about half a second longer and investigated", + "isCorrect": true + }, + { + "text": "An automated scanner flagged the malicious code during code review", + "isCorrect": false + }, + { + "text": "The attacker's account was caught bragging about it on a forum", + "isCorrect": false + }, + { + "text": "Servers around the world were breached, and investigators traced the break-ins to xz", + "isCorrect": false + } + ], + "explanation": "On 29 March 2024, Andres Freund, a Microsoft engineer and PostgreSQL developer, posted to the oss-security mailing list. SSH logins on his Debian test system were using too much CPU, producing Valgrind errors, and taking 0.8 seconds instead of about 0.3. He traced the problem to xz versions 5.6.0 and 5.6.1, which contained a backdoor aimed at the SSH server (CVE-2024-3094).\n\nThe story matches the comic. For years, one volunteer maintained xz. From 2021, an account called \"Jia Tan\" sent helpful patches while other accounts pressured the maintainer about slow releases. Jia Tan became a co-maintainer and then shipped the backdoor, hiding it in test files and release tarballs instead of the visible source.\n\nA person caught it, not an automated tool, and before most stable Linux releases shipped the affected versions. That's why it's often called a near miss. The next backdoor might not cause a half-second delay.", + "references": "[xkcd #2347: Dependency](https://xkcd.com/2347/), [explain xkcd: 2347](https://www.explainxkcd.com/wiki/index.php/2347), [Openwall oss-security: backdoor in upstream xz/liblzma](https://www.openwall.com/lists/oss-security/2024/03/29/4), [Wikipedia: XZ Utils backdoor](https://en.wikipedia.org/wiki/XZ_Utils_backdoor)" + }, + { + "id": "which-comics-came-true", + "title": "Which of these statements about xkcd comics and the real world are true?", + "type": "multiple-choice", + "options": [ + { + "text": "Flickr released a working bird detector less than a month after #1425 called it a five-year research problem", + "isCorrect": true + }, + { + "text": "NIST's 2017 guidelines dropped the character-mix rules that #936 made fun of", + "isCorrect": true + }, + { + "text": "The EU made one charging port mandatory, the kind of fix #927 said voluntary standards couldn't deliver", + "isCorrect": true + }, + { + "text": "#1354 explained Heartbleed before the bug was publicly disclosed", + "isCorrect": false + }, + { + "text": "The spacebar-heating bug in #1172 was a real bug in a released program", + "isCorrect": false + }, + { + "text": "Hyrum's Law is named after a character in an xkcd comic", + "isCorrect": false + } + ], + "explanation": "Three had real follow-ups:\n\n- **#1425, \"Tasks\".** Flickr's Park or Bird appeared 26 days after the comic, built on a deep network Flickr already had.\n- **#936, \"Password Strength\".** NIST's SP 800-63B told sites to stop imposing composition rules and forced periodic changes. The comic didn't cause that. Research on how people actually pick passwords did, and it reached the same conclusion as the comic.\n- **#927, \"Standards\".** Directive (EU) 2022/2380 made USB-C the common charging port from the end of 2024. The comic's cycle only broke because a law took the choice away.\n\nThe other three are false. #1354 appeared four days *after* Heartbleed was disclosed, so it explained the bug instead of predicting it. The spacebar-heating bug is fiction, invented to illustrate a real pattern. Hyrum's Law is named after Hyrum Wright, a software engineer then at Google, and describes that same pattern independently of the comic.", + "references": "[xkcd #1425: Tasks](https://xkcd.com/1425/), [explain xkcd: 1425](https://www.explainxkcd.com/wiki/index.php/1425), [xkcd #936: Password Strength](https://xkcd.com/936/), [explain xkcd: 936](https://www.explainxkcd.com/wiki/index.php/936), [xkcd #927: Standards](https://xkcd.com/927/), [explain xkcd: 927](https://www.explainxkcd.com/wiki/index.php/927), [xkcd #1354: Heartbleed Explanation](https://xkcd.com/1354/), [explain xkcd: 1354](https://www.explainxkcd.com/wiki/index.php/1354), [Hyrum's Law](https://www.hyrumslaw.com/)" + } + ] +} diff --git a/content/quizzes/xkcd-predicted-it/bobby-tables-anatomy.svg b/content/quizzes/xkcd-predicted-it/bobby-tables-anatomy.svg new file mode 100644 index 0000000..c7ca175 --- /dev/null +++ b/content/quizzes/xkcd-predicted-it/bobby-tables-anatomy.svg @@ -0,0 +1,74 @@ + + + + + How one name becomes two SQL statements + + 1. The program pastes the name into a query template: + + INSERT INTO Students (name) VALUES ('…name…'); + + 2. With the name Robert'); DROP TABLE Students;-- the database receives: + + INSERT INTO Students (name) VALUES ('Robert'); DROP TABLE Students;--'); + + + '); + Quote closes the string. + Bracket and semicolon + finish the INSERT. + + + DROP TABLE …; + A second statement + that deletes the + whole table. + + + -- + Comments out the + template's leftover '); + so nothing errors. + + The fix: send the SQL and the name separately, so the name can never run as SQL. + diff --git a/content/quizzes/xkcd-predicted-it/park-or-bird-tasks.svg b/content/quizzes/xkcd-predicted-it/park-or-bird-tasks.svg new file mode 100644 index 0000000..e941af6 --- /dev/null +++ b/content/quizzes/xkcd-predicted-it/park-or-bird-tasks.svg @@ -0,0 +1,72 @@ + + + + + Two questions about one photo, 2014 + + + Taken in a national park? + Input: GPS coordinates in the + photo's metadata + Method: is the point inside a + park boundary polygon? + Needs: a map and a lookup + Comic: “a few hours” + + + Is it a bird? + Input: millions of pixel values + Method: learn what beaks, wings + and feathers look like from + millions of labelled examples + Comic: “a research team + and five years” + + + + + Sep 24: xkcd #1425 + Oct 20: Flickr's Park or Bird + 26 days + Park or Bird reused a deep network Flickr had already trained. + diff --git a/content/quizzes/xkcd-predicted-it/passphrase-entropy-bars.svg b/content/quizzes/xkcd-predicted-it/passphrase-entropy-bars.svg new file mode 100644 index 0000000..3c330c4 --- /dev/null +++ b/content/quizzes/xkcd-predicted-it/passphrase-entropy-bars.svg @@ -0,0 +1,74 @@ + + + + + Password strength in bits, as xkcd #936 counts it + Each bit doubles the guesses. The attacker knows the method and makes 1,000 guesses a second. + + Tr0ub4dor&3 + a common word, tweaked + + ~28 bits · ~3 days + + correct horse battery staple + four random words from 2,048 + + + + + 11 bits + 11 bits + 11 bits + 11 bits + 44 bits · ~550 years + + + 0 + 11 + 22 + 33 + 44 + + 16 more bits means 2¹⁶, or 65,536, times as many guesses. + Bar lengths are to scale. +