
Here’s some notes on how to use the 1980s classic spreadsheet program sc, and
why. We’ll start with the usage basics, then discuss the limitations and comment
on the user experience.
Install it from your favorite package manager (sudo apt install sc) or build
the latest version yourself:
git clone https://github.com/n-t-roff/sc.git
cd sc
./configure
make
sudo make installLaunch sc and you’ll have a spreadsheet in milliseconds—likely faster than
your finger can leave the Enter key.
Navigation. To move around, use the arrow cells or the hjkl keys familiar
from Vim and similar programs. To quit, press q. To save, press P.
Text Entry. Free-form text can be inserted in the current cell using <` for
left-aligned text, `> for right aligned, and \ for centered. To change
alignment subsequently, navigate to the cell and press { or } to left or
right align, and | to center.
Annotations. Instead of adding hidden notes to cells, in sc one adds links
to other cells. (I tend to place these notes in the rightmost column, so they do
not clutter the main data.) To add a link, press *a, navigate to the target
cell, and press Enter twice. Press ** to jump from the original cell to the
linked target cell. Simple!
Numerical Values. Press = and enter a number or a formula. Special
functions start with @, for example entering @now will enter the current
Unix time. To enter a specific date, use @dts(2026,11,30).
Cell Format. Press F to set the format string for the current cell. The
manual details the format, but in practice I use this most commonly to make one
cell (or a few cells) format a date/time value. I use the format string
^D%m/%d/%y, where ^D is the literal Ctrl-D character (ASCII 0x04, “end of
transmission”).
Column Format. Press f and use the arrow keys or hjkl: left/right (or
h/l) to make the column narrower or wider; up/down (or k/j) to increase
or decrease the number of decimal places.
For more flexibility, press f followed by a digit: f0 is the default, f1
formats numbers in scientific notation like 1.00E+04, f2 is the same in
engineering notation (10.00e+03 such that the exponential is a multiple of
three), f3 and f4 format dates as 5 Nov 26 and 5 Nov 2026, resp. To
define a custom numerical format, open spreadsheet in the text editor and enter
a line like
format 5 = "^D%m/%d/%y"Careful: if you want the date formatting to work correctly, the ^D must be the
literal end-of-transmission character, not a caret followed by uppercase D.
Rows/Cols. To insert new rows or columns, type ir or ic respectively. To
delete rows or columns, type dr and dc. As with most commands, you can
precede it with a number; for example, 5ir inserts five rows. Careful when
deleting multiple rows, there is no undo! Save the sheet first, before making
large deletions.
Ranges. To operate on several cells at once, use the range
commands that start with r. There are several range commands but I only use
four:
Delete a range: type rx then use motion commands (such as the arrow keys) to
select the range, followed by Enter.
Convert formulas to values: rv + motion + Enter and the formulas are
replaced with their numeric values.
Copy a range: navigate to the destination cell and type rc followed by
spacebar. Then use motion commands to navigate to the first cell in the source
range. Press . (period) to begin range selection. Press Enter to execute the
copy.
Move a range: starting on the destination cell, type rm. Then go to the
source, press . (period), select the range you moved moved, and press Enter
to execute the move.
Copy/Cut/Paste. Besides the range copy and move commands explained just above, it is also possible to operate on a single cell, or one or more rows or columns, as follows:
| Object | Copy | Delete | Paste |
|---|---|---|---|
| Cell | yy | x | pp |
| Row | yr | dr | pr |
| Column | yc | dc | pc |
Marks and Jumping. When working on two parts of the spreadsheet that are far
apart, for example when copying and pasting, or moving things around, it helps
to give cells a temporary “mark”. To mark a cell, press m followed by any
other lowercase letter, for example ma. To jump to a cell previously marked in
this way, press backtick followed by the same lowercase letter.
To copy the marked cell, mark it, navigate somewhere else, and press c
followed by the letter. Even though one can copy and paste a single cell using
yy and pp as explained above, I got used to copying with ma and ca
instead (Y is far away from fingers on QWERTY keyboards).
Type Ctrl-A to jump to cell A0.
Scrolling. To scroll the sheet down, press Ctrl-e followed by Ctrl-e
(again). To scroll up, press Ctrl-y. To scroll such that the current cell
becomes the top row, press z followed by Enter. To center the current cell,
press zc.
Searching. To search for a particular string (case sensitive), type g"
followed by the desired string. To repeat the search without re-typing the
string, press n. To repeat the search in the backwards direction, type N.
Colors. There are a number of commands to set the color of cells. I do not remember what these are. Instead, I open the spreadsheet file in a text editor, and simply add the following self-explanatory lines:
set color
color 1 = @white;@black
color 2 = @black;@green
color A1:C4 2Frames. Set a frame if you want one part of the spreadsheet to scroll independently of another. I use this to have the table header with column names always visible, even when scrolling through many pages of data rows. There are many commands for manipulating frames and as with colors, I do not remember any of them. Instead, open the spreadsheet in a text editor and add a line like
frame A0:N1000 B3:N1000This sets the first three lines to be the fixed header, while the rest scrolls freely.
Function keys. To use shortcuts, the function keys (F1 through F12) can be given custom actions, such as motion commands or calling macros. For example:
fkey 4 = "endright"
fkey 11 = "merge \"|~/projects/config/scmacros/copy_above.sh\""Macros. To convert a complex sequence of keystroke into a single keypress,
define macros. These are simple shell scripts such as the
copy_above.sh
that I have mapped to F11: it moves one cell up and one cell right, copies the
cell, then moves one cell down to paste it. To create your own, take a look at
my macros, or, better
yet, point your favorite AI at the cloned sc repo and tell it to write it for
you.
Technically sc distinguishes “simple” macros, which are interpreted by sc
itself, and advanced macros, which are shell filters. However, the advanced
macros can do anything the simple ones do, so I never used the simple ones.
Config file. Common settings that you want applied by default to all
spreadsheets can be placed in the ~/.scrc file. Rather than reading this file
in addition to the spreadsheet file, sc will place the content of scrc into
all newly created spreadsheets. In my
scrc, you will find only
the format 5 definition and a couple function key shortcuts.
A key part of the sc experience lies in the features that are not present.
Of course, you could add any of these with AI, or look at projects like
sc-im but in this instance I choose
to “embrace the constraint” instead, to see what I can learn from it.
No Sync. Instead of every change being reflected on the cloud, I sync the sheets I want to sync by adding them to a git repo. Works great since the file format is text-based and very simple.
No Multiple Sheets. I was used to grouping several related sheets in one
file, but after sc-ing for a while, this makes no more sense to me than
bundling several source code files in one. The file system works great for
organizing files—why does it seem like people are suddenly afraid of using
files? Why do Apple and Google go to such lengths to hide it from users?
Limited Conditional Formatting. I love making sheets automatically get colored based on data values but in retrospect it’s a huge distraction. Why not make the data speak for itself instead? (There are some commands that can change cell color, but I do not find it useful enough to learn.)
UI is not Discoverable. You cannot figure out how to do things, or even if something is possible, except by reading the manual and/or source code. The learning code feels steep but for a program I use every day, it totally does not matter. I have it all in finger memory and I’m glad there’s not “beginner friendly” menus, popups, AI chats slowing me down.
Limited External Copy/Paste. It’s nice to copy a table from a web page seamlessly into a spreadsheet but here you have to import it with a text editor, or Python script, or Claude. No CSV in/out either, without external tools. In other words, use the right tool for the right job. Does one program really have to do everything? For me, the key insight of Unix is the partitioning of tasks into separate small programs, even if sometimes at the cost of slight inconvenience.
No Undo/Redo. Instead, I keep history with git, and make sure to save
before and after making edits. If I make a mistake, just quit and reopen the
program: qn, Up, Enter. Four keystrokes and it’s undone. (Usually Ctrl-Z
in Google Sheets takes longer to execute, amazingly.)
No Unicode. I live in ASCII.
In light of these limitations, why bother with sc? Isn’t old software
obsolete, by definition of “old”? I disagree. If a program worked well on
ancient computers a long time ago, then on modern machines it will work even
better. The sc user experience is just so fast it blew me away. There is
simply no waiting; everything happens instantaneously: opening/closing the
program, entering data, making edits.
Conversely, if a piece of software—indeed any technical project–is worth implementing, it’s worth doing it right so it can stand the test of time. Sure, some tech is necessarily disposable, if it interfaces with some frequently changing protocols, for example. But most problem domains allow decomposition into small tools, where the tool remains essentially unchanged over decades.
The user experience deserves particular focus in the earliest stages of design, to ensure that it does not irritate or slow down the advanced user. If a tool is supposed to be used frequently by most of its users, then most of its users will become “advanced users” and will not tolerate convoluted keystrokes or, worse, time-consuming mouse or touch navigation. And certainly there is no excuse for slow user interfaces—that ship sailed back when processor clock speeds exceeded a megahertz.
Android is a particularly nasty counterexample: in a misguided attempt to
appease the “beginner”, everything is hidden behind seven layers of tiny
hamburger menu icons. Essentially all web apps take several orders of magnitude
too many clock cycles for any actions. The problem has become so bad and so
pervasive that no one even notices anymore—everything is slow, slow slow. But
programs such as sc can wake us up to realize: not everything has to
suck.
I’m no longer convinced anybody knows anything about user interface. It’s clear some things are easier to use and some things are harder to use. That’s okay. But it’s also clear that people learn a lot.[1]

When the rules of a game are simple, don’t keep inventing new rules to complicate things. In particular, at all levels an engineering project needs to be as simple as possible while still functioning as expected. Beyond that, leave all rules behind.
Indeed, there is only one rule: the thing has to work. Besides, the less time and effort spent in the implementation, the more there is left for interesting applications. In sum, always only make the minimum working example.

These are some notes I’ve taken when creating a circuit that takes a 10 MHz sine-wave input and outputs several copies of the same, with a minimum of added phase noise or distortion. First, select a topology and simulate it. Then, build the circuit and measure the key specs: noise, crosstalk, distortion.
The goal is simple, 10 MHz in, 10 MHz out. Many approaches have been explored:
Take a differential 10 MHz input signal, pass it through a difference amplifier, output a single ended signal.
Start with 20 MHz, pass it through a flip-flop to divide by 2, and filter the resulting square wave to kill the harmonics and obtain a sine wave.
Design a differential amplifier using discrete transistors to drive a signal transformer to lower the output impedance.
Emitter follower, with AC coupled output (series LC).
Inverting op-amp plus some filtering and the impedance transformer at the output.
Non-inverting op-amp output circuit.
Of these, I have chosen the version that I find easiest to explain to myself: an inverting buffer drives several non-inverting output drivers. This is shown in the image below.

A straightforward transient LTspice simulation (.tran 0 1.5u 0.5u 25p) shows
that the output amplitude is 1Vrms. In other words, the circuit should work as
shown.
In the simulation I used the spice model for the LMH6609 op-amp provided by
Texas Instruments (original model by National Semiconductor). It works just fine
in LTspice except for commenting out a comment. Apparently PSPICE supports the
/ symbol as comment in addition to the * used by LTspice.
A key spec on any multi-channel instrument is crosstalk: if a signal is injected in one output, does it show up in the other output?
Notice the V4 signal source in the LTspice schematic shown above. If we
disable the main signal source V3 to AC 0 and enable V4 as AC 1, then an
AC simulation (.ac dec 200 1k 1G) shows about 10μV of crosstalk at out1.
Including a rough estimate for layout-dependent capacitive coupling between two
outputs directly (e.g. 10fF at C7), this rises to 17μV, or about -95 dB.
Assuming the op-amp model is accurate, we can use this result to estimate the phase pulling between the two outputs. Assume one of the outputs is connected to an unterminated cable such that the full signal strength—all 1Vrms or +13dBm of it—comes back in quadrature. The peak amplitude is 2.82 × 17 μV = 48μV.
Adding a small phasor a at angle φ to a carrier A gives a resultant phase
shift Δφ ≈ (a/A)·sin φ. The angle φ is set by the cable’s round-trip delay
plus the coupling path’s fixed phase—and since the user can present any
length, the worst case is φ = 90° (quadrature), where the interferer is pure
phase: Δφ = 48 µV / 1.38 V = 35 µrad.
At 10MHz one period is 100ns spanning 2π rad, so Δt = Δφ/(2πf) = 34e-6 /
(6.28e7) ≈ 0.55 ps. The reflection on Ch-2 will cause Ch-1 to move in phase by
less than a picosecond. Pretty good!
There are many mechanisms that contribute to the phase noise at our outputs. Let us immediately ignore the noise of the 10 MHz source and instead focus on what adds to the source’s phase noise.
At large offsets away from carrier, the noise floor is set by op-amp voltage
noise, resistor Johnson noise, and current noise. The same LTspice model from
above (remember to disable V4 and re-enable V3!) can be used to calculate
the voltage noise density at the output, by means of a .noise V(out1) V3 lin
401 9.9Meg 10.1Meg simulation directive. At 10 MHz, it reads 9.7 nV/rtHz.
Relative to the 1 Vrms output amplitude, this is a phase noise floor of
L = 20 log10(9.7e-9 / 1.00) − 3.01 = −163 dBc/Hz.Note the 3 dB subtracted, due to the fact that the noise decomposes into components in phase and in quadrature with the carrier phasor. The in-phase part wiggles the amplitude; the quadrature part wiggles the phase. For additive noise these are equal in power, so exactly half the noise power becomes phase modulation.
Any air moving across the board, whether forced or natural convection, can cause the temperature of PCB components to fluctuate. Ferrite inductors, such as the 1.5 μH seen in our tank circuit, might have as much as 500 ppm/K of temperature coefficient. We can step the inductance value in the LTspice simulation to find about 0.45° of phase change due to 1% change in inductance.
Putting the numbers together, the phase sensitivity is 3.9e-4 rad/K, or, in time units, about 6.3 ps/K. If we estimate the inductors temperature noise at 1 Hz to be about 1.5 mK/rtHz, then the phase noise would be
L = 20 log10(3.9e-4 * 1.5e-3) - 3.01 = -127 dBc @ 1 Hz offset.That’s a significant amount of phase noise, but easily fixed by reducing the air
currents or increasing thermal insulation. The inductor temperature changes are
heavily low-pass filtered by means of the part’s thermal inertia. In other
words, above the thermal pole the response falls as f^{-2}, so lowering the
corner buys 20 dB per decade of shift.
Any sound or mechanical vibration modulates the electrical characteristics of the circuit. Non-NP0 capacitors are particularly affected, so it’s important to verify there’s none in the critical signal path.
Inductors are also prone to microphonics, via magnetostrictive modulation of inductance. The datasheet will most likely not specify it, so instead we take a wild guess, say 1.5e-4 inductance change per “g” of acceleration.
Reusing the 0.45° per 1% coefficient from the previous section, the phase sensitivity is 1.2e-4 rad/g, or about 1.9 ps/g. A fan blade-pass tone at 120 Hz depositing 0.01 g peak gives a peak phase deviation of 1.2 μrad, and a sinusoidal modulation of peak deviation Δφ produces sidebands at
20 log10(1.2e-6 / 2) = -124 dBc.
Disclaimer: I’m not a theologian, proceed at your own risk.
An astute reader of the Notes on Cassian noticed a wild claim of mine, that “being dead to the wold” means afterlife, when examining Cassian’s thoughts on divine contemplation and “seeing God”, in Conf. 1, Ch. XV. Let’s take a closer look.
I don’t think you can “see God”, no matter how “pure” or “dead to the world” you are. To be fair, Cassian only makes the converse claim: if, any carnal affections survive in you, then you cannot lastingly contemplate God. Am I going too far by assuming Cassian implied the reverse claim: you could lastingly contemplate God if only no carnal affections survive in you?
A parallel set of claims arises from his scriptural quote. Exodus states that if you see God, then you cannot live. Cassian elaborates: you cannot live to “this world and to earthly affection” (mundo huic affectibusque terrenis). The implied reverse statement in this case is, if you’re “dead to the world”, then you may be able to see God. Again, did Cassian intend this implication or am I reading into it too far?
There’s only two options, either he did mean it or he did not. If he did not mean it, then we could discuss whether carnal affections (carnalis affectus) prevent you from contemplating God “lastingly” (perpetuo), or from seeing God. Since I’m not at this point interested in studying perpetuity, i.e., eternity or afterlife, so let’s pass on to the other option: Cassian did mean the “reversed claim”: purify your heart and you’ll be able to see God now and forever.

Some time ago, models used to take a widely variable time to complete a task, ususally between ten seconds and ten minutes. This kept me glued to the screen, mostly just waiting for it to be done. Then I got the idea to tell Claude that when it’s done, it should print a big “DONE” banner so I can see it from the other side of the room. I ended up collecting several different versions of this banner as they came out of the model.
██████ ██████ ███ ██ ███████
██ ██ ██ ██ ████ ██ ██
██ ██ ██ ██ ██ ██ ██ █████
██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██ ████ ████████████ ██████ ███ ██ ███████
██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ █████
██ ██ ██ ██ ██ ██ ██ ██
█████ ██████ ██ ████ ████████████ ██████ ███ ██ ███████
█ ██ ██ ██ ██ ██ ██
█ ██ ██ ██ ██ ██ ██ █████
█ ██ ██ ██ ██ ██ ██ ██
████ ██████ ██ ████ ████████████ ██████ ███ ██ ███████
██ ██ ██ ██ ███ ██ ██
██ ██ ██ ██ ██ ██ ██ █████
██ ██ ██ ██ ██ ██ ██ ██
█████ ██████ ██ ████ ████████████ ██████ ███ ██ ███████
██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ █████
██ ██ ██ ██ ██ ██ ██ ██
█████ ██████ ██ ████ ████████████ ██████ █ █ █████
█ █ █ █ ██ █ █
█ █ █ █ █ █ █ █████
█ █ █ █ █ █ █ █████
█ █ █ █ █ █ █ █
█████ ██████ █ █ ██████████ ██████ █ █ █████
█ █ █ █ ██ █ █
█ █ █ █ █ █ █ █████
█ █ █ █ █ █ █ █
█ █ █ █ █ ██ █
█████ ██████ █ █ ██████████ █████ █ █ █████
█ █ █ █ ██ █ █
█ █ █ █ █ █ █ ████
█ █ █ █ █ ██ █
█████ █████ █ █ ██████████ ████ █ █ █████
█ █ █ █ ██ █ █
█ █ █ █ █ █ █ ███
█ █ █ █ █ ██ █
█████ ████ █ █ █████████ ████ █ █ █████
█ █ █ █ ██ █ █
█ █ █ █ █ █ █ ███
█ █ █ █ █ ██ █
████ ████ █ █ █████████ ████ █ █ █████
█ █ █ █ ██ █ █
█ █ █ █ █ █ █ ███
█ █ █ █ █ ██ █
████ ████ █ █ ████████ ███ ██ █ █ ████
█ █ █ █ █ █ ██ █
█ █ █ █ █ █ ██ ███
█ █ █ █ █ █ █ █ █
███ ███ █ █ █ █ ████