<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/"
>

<channel>
	<title>Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Mon, 31 Aug 2026 18:38:25 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wadetregaskis.com/wp-content/uploads/2016/03/Stitch-512x512-1-256x256.png</url>
	<title>Wade Tregaskis</title>
	<link>https://wadetregaskis.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">226351702</site>	<item>
		<title>Workaround for a macOS bin that refuses to empty</title>
		<link>https://wadetregaskis.com/workaround-for-a-macos-bin-that-refuses-to-empty/</link>
					<comments>https://wadetregaskis.com/workaround-for-a-macos-bin-that-refuses-to-empty/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 31 Aug 2026 18:38:22 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bin]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Finder]]></category>
		<category><![CDATA[macOS]]></category>
		<category><![CDATA[Trash]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=9284</guid>

					<description><![CDATA[Sometimes when I tell macOS to empty the bin (&#8220;trash&#8221; if you&#8217;re United Statesian) it refuses with a baffling error message: I have no idea what causes this. One solution is to restart the affected Mac &#8211; but that&#8217;s obviously very disruptive. Another solution is to open the bin in the Finder (which works just&#8230; <a class="read-more-link" href="https://wadetregaskis.com/workaround-for-a-macos-bin-that-refuses-to-empty/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Sometimes when I tell macOS to empty the bin (&#8220;trash&#8221; if you&#8217;re United Statesian) it refuses with a baffling error message:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img fetchpriority="high" decoding="async" width="516" height="224" src="https://wadetregaskis.com/wp-content/uploads/2026/08/finder-refusing-to-empty-the-trash.webp" alt="Screenshot of a macOS error dialog, from the Finder, that says &quot;&quot;Bin&quot; can't be opened right now because it's being used by another task, such as moving or copying an item or emptying the Bin. Try again when the current task is complete.&quot;." class="wp-image-9285" srcset="https://wadetregaskis.com/wp-content/uploads/2026/08/finder-refusing-to-empty-the-trash.webp 516w, https://wadetregaskis.com/wp-content/uploads/2026/08/finder-refusing-to-empty-the-trash-256x111.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/08/finder-refusing-to-empty-the-trash@2x.webp 1032w, https://wadetregaskis.com/wp-content/uploads/2026/08/finder-refusing-to-empty-the-trash-256x111@2x.webp 512w" sizes="(max-width: 516px) 100vw, 516px" /></figure>
</div>


<p class="wp-block-paragraph">I have no idea what causes this.  One solution is to restart the affected Mac &#8211; but that&#8217;s obviously very disruptive.</p>



<p class="wp-block-paragraph">Another solution is to open the bin in the Finder (which works just fine, contrary to what the error message implies), select all, right-click, and choose &#8220;Delete immediately…&#8221;.  And the bin is now empty. 🎉</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="823" height="587" src="https://wadetregaskis.com/wp-content/uploads/2026/08/delete-immediately…-from-the-bin.webp" alt="Screenshot of a Finder window showing the bin, which contains four miscellaneous files, with the right-click contextual menu showing and &quot;Delete Immediately…&quot; highlighted." class="wp-image-9286" srcset="https://wadetregaskis.com/wp-content/uploads/2026/08/delete-immediately…-from-the-bin.webp 823w, https://wadetregaskis.com/wp-content/uploads/2026/08/delete-immediately…-from-the-bin-256x183.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/08/delete-immediately…-from-the-bin-768x548.webp 768w, https://wadetregaskis.com/wp-content/uploads/2026/08/delete-immediately…-from-the-bin@2x.webp 1646w, https://wadetregaskis.com/wp-content/uploads/2026/08/delete-immediately…-from-the-bin-256x183@2x.webp 512w" sizes="(max-width: 823px) 100vw, 823px" /></figure>
</div>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/workaround-for-a-macos-bin-that-refuses-to-empty/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/08/finder-refusing-to-empty-the-trash.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">9284</post-id>	</item>
		<item>
		<title>The Apple Calculator Language</title>
		<link>https://wadetregaskis.com/the-apple-calculator-language/</link>
					<comments>https://wadetregaskis.com/the-apple-calculator-language/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 30 Jul 2026 02:31:37 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[ALGOL]]></category>
		<category><![CDATA[APL]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[BASIC]]></category>
		<category><![CDATA[Cobol]]></category>
		<category><![CDATA[Fortran]]></category>
		<category><![CDATA[Jef Raskin]]></category>
		<category><![CDATA[Lisp]]></category>
		<category><![CDATA[Macintosh]]></category>
		<category><![CDATA[Pascal]]></category>
		<category><![CDATA[PL/1]]></category>
		<category><![CDATA[RealBasic]]></category>
		<category><![CDATA[SNOBOL]]></category>
		<category><![CDATA[The Apple Calculator Language]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=9214</guid>

					<description><![CDATA[While perusing Jef Raskin&#8217;s documents from the early days of Apple (1979 predominately), I came across a curious one: a &#8220;calculator&#8221; language that Jef proposed. What&#8217;s particularly interesting is that it&#8217;s presented in the context of programming languages. Its specification doesn&#8217;t really describe a general purpose programming language &#8211; the title is accurate in that&#8230; <a class="read-more-link" href="https://wadetregaskis.com/the-apple-calculator-language/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">While perusing <a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/bom/index.html" data-wpel-link="external" target="_blank" rel="external noopener">Jef Raskin&#8217;s documents from the early days of Apple</a> (1979 predominately), I came across a curious one: a &#8220;calculator&#8221; language that Jef proposed.</p>



<p class="wp-block-paragraph">What&#8217;s particularly interesting is that it&#8217;s presented in the context of <em>programming</em> languages.  Its specification doesn&#8217;t really describe a general purpose programming language &#8211; the title is accurate in that it is a calculator language.  But in context it seems implied that Jef had aspirations of turning it into something bigger.  Something that might have been <em>the</em> programming language for the Macintosh (which turned out, instead, to be Pascal).</p>



<p class="wp-block-paragraph">Furthermore, there&#8217;s an extensive addendum presenting Jef&#8217;s opinions on programming languages of the day &#8211; ALGOL, APL, BASIC, Cobol, Fortran, Lisp, Pascal, PL/1, SNOBOL &#8211; which I guess is what Jef felt were his &#8216;competition&#8217; in some sense, or at least his predecessors; he clearly had aspirations towards creating a new language of his own.  In time-honoured fashion, this is because he felt all existing languages weren&#8217;t good enough. 😆</p>



<p class="wp-block-paragraph">Jef was <em>especially</em> not a fan of BASIC (which was the lingua franca at the time, for personal computers &#8211; ignoring assembly languages, at least) and didn&#8217;t mince words about it:</p>



<figure class="wp-block-pullquote"><blockquote><p>BASIC is a very weak language, and is being shored up in many ad hoc ways. Any student of programming languages can easily poke holes in it. Nonetheless, it will continue to be used for many years, which is a tribute to its environment, not its internal design. BASICs well-deserved popularity demonstrates that minimizing programmer effort and time is often more important, when it comes to purchasing a language, than the language&#8217;s features as described in the spirit of traditional computer science.</p><cite>Jef Raskin, <a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/bom/language.html" data-wpel-link="external" target="_blank" rel="external noopener">The Apple Calculator Language Primer</a></cite></blockquote></figure>



<p class="wp-block-paragraph">He wasn&#8217;t wrong about its longevity.  I wonder what he later thought (he lived until 2005) of Visual Basic, RealBasic, and all their derivatives…</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">❤️ My first commercial software was written in RealBasic. I still remember it fondly. RealBasic as a language, IDE, and set of standard libraries was simple and elegant. If a bit slow (oh, what we could do with it now with modern compilers or JITs!).</p>
</div></div>



<p class="wp-block-paragraph">On Fortran, PL/1, and Pascal &#8211; the latter being used heavily on the Macintosh, until Mac OS X &#8211; he seemed… bitter?  A little resentful of their success?  Of them, he said:</p>



<figure class="wp-block-pullquote"><blockquote><p>As declared languages they are inherently poor at simulating human trains of thought. I am well aware of the advantages of declarations, and take joy at the improvement in control structures of, say Pascal over FORTRAN. But these are all improvements of a detailed, nit-picking sort. What languages should do is remove the nits, and minimize the detail you must think about when solving problems. What Pascal&#8217;s data structures, declarations, and control structures have done is to make those details more explicit, and thus cleaner. It is a real improvement, but unfortunately leans in the direction of discipline and rigidity.<br><br>Pascal is often pointed out as a modern, well designed language. It is, to be sure, tolerably efficient. Its efficiency is due to many design choices aimed at making life easier for the compiler writer and system programmer. The user is often ignored.</p><cite>Jef Raskin, <a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/bom/language.html" data-wpel-link="external" target="_blank" rel="external noopener">The Apple Calculator Language Primer</a></cite></blockquote></figure>



<p class="wp-block-paragraph">He didn&#8217;t think much of Lisp, either, which heavily inspired Smalltalk and in turn Objective-C.</p>



<figure class="wp-block-pullquote"><blockquote><p>LISP … is an interesting corner of the programming universe, and is relatively efficient of programmer time. It suffers from an overdose of recursion and a lack of approachableness for common, everyday activities.</p><cite>Jef Raskin, <a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/bom/language.html" data-wpel-link="external" target="_blank" rel="external noopener">The Apple Calculator Language Primer</a></cite></blockquote></figure>



<p class="wp-block-paragraph">But on APL, he really can&#8217;t say enough good things &#8211; he has an entire section on singing its praises (as he saw them).  It&#8217;s too much to quote here, but I suggest you read it.</p>



<p class="wp-block-paragraph">While it&#8217;s true that APL did <em>somewhat</em> influence C/C++, and arguably Python, overall APL proved to be a dead-end in the programming language evolutionary tree.  Looking back at it now, with modern sensibilities, its syntax looks like a parody, an absurdism &#8211; something someone invented so they could basically cheat on the <a href="https://www.ioccc.org" data-wpel-link="external" target="_blank" rel="external noopener">Obfuscated C Code Contest</a>.</p>



<p class="wp-block-paragraph">Which &#8211; and thank you for bearing with me as I meander to my point &#8211; is why it&#8217;s all the more startling to me that his little &#8220;Apple Calculator Language&#8221; is actually so darn appealing.  The simplicity of left-to-right evaluation, the surprising elegance of his &#8220;clumps&#8221;… there&#8217;s something about this which I find surprisingly refreshing.</p>



<p class="wp-block-paragraph">Reading about it in his spec is a bit dry, so I &#8211; well, Claude Opus 5 &#8211; built a working implementation for our amusement!</p>



<div id="acl">
  <div class="acl-sheet">

    <header class="acl-head">
      <h1 class="acl-title" style="padding-top: 0.5em">The Apple<br>Calculator Language</h1>
      <p class="acl-byline">Jef Raskin &middot; 13 October 1979</p>
      <p class="acl-lede">A calculator you talk to in sentences. It reads strictly from left
        to right, it has no idea that multiplication is supposed to go first, and it
        treats a fistful of numbers as one thing, which Raskin called a <em>clump</em>.
        This is that language, running.</p>
      <p class="acl-readmore"><a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/bom/language.html" target="_blank" rel="noopener noreferrer external" data-wpel-link="external">Read
        Raskin&#8217;s original primer at Stanford</a> &rarr;</p>
      <a class="acl-stamp" href="https://archives.stanford.edu/catalog/m1007" target="_blank" rel="noopener noreferrer external" aria-label="Finding aid for the Apple Computer, Inc. Records, M1007, Stanford University Libraries" data-wpel-link="external">
        <span>M1007</span><span>Series 3 &middot; Box 10 &middot; Folder 1</span><span>Dept. of Special Collections</span>
      </a>
    </header>

    <div class="acl-body">

      <!-- ============ the machine ============ -->
      <section class="acl-machine" aria-label="Calculator">
        <div class="acl-panel">
          <h2 class="acl-panelhead">Paper tape</h2>
          <div class="acl-tape" id="acl-tape" role="log" aria-live="polite" aria-atomic="false"></div>

          <div class="acl-entry">
            <span class="acl-prompt" id="acl-prompt" aria-hidden="true">!</span>
            <label class="acl-sr" for="acl-in">Type an expression and press Return</label>
            <input id="acl-in" class="acl-in" type="text" autocomplete="off" autocorrect="off"
                   autocapitalize="off" spellcheck="false" enterkeyhint="go">
            <button class="acl-return" id="acl-return" type="button">Return</button>
          </div>

          <div class="acl-status">
            <span class="acl-chip"><b>PLACES</b> <i id="acl-places">2</i></span>
            <span class="acl-chip"><b>RADIANS</b> <i id="acl-radians">1</i></span>
            <span class="acl-chip acl-chip-wide"><b>stored</b> <i id="acl-names">nothing yet</i></span>
            <span class="acl-spacer"></span>
            <button class="acl-mini" id="acl-clear" type="button">Clear the tape</button>
            <button class="acl-mini" id="acl-forget" type="button">Forget every name</button>
          </div>
        </div>
      </section>

      <!-- ============ the examples ============ -->
      <section class="acl-examples" aria-label="Starting points">
        <h2 class="acl-panelhead acl-panelhead-loose">Starting points</h2>
        <p class="acl-hint">Press one and it runs. It also drops into the entry line so you can
          take it apart.</p>
        <div id="acl-cards"></div>
      </section>
    </div>

    <!-- ============ the card ============ -->
    <details class="acl-ref" open>
      <summary>The whole language on one card</summary>
      <div class="acl-refbody">

        <div class="acl-refcol">
          <h3>How it reads</h3>
          <p>Strictly left to right. <code>6/3+2*5</code> is six, divided by three, plus two,
            times five, which is <b>20</b>. Parentheses group things that should be settled first.</p>
          <p>Numbers written side by side make a <b>clump</b>: <code>34 5 67</code>. An operation
            applies to the whole clump on its left, but only takes <em>one</em> element from its
            right. That is why <code>1 2 3 4+4 3 2 1</code> gives <b>5 6 7 8 3 2 1</b>.</p>
          <p>Negative numbers wear an underscore, so subtraction never gets confused with them:
            <code>_45.4</code>. Braces hold notes to yourself and are ignored:
            <code>{like this}</code>.</p>
        </div>

        <div class="acl-refcol">
          <h3>Between two things</h3>
          <dl class="acl-defs">
            <dt><code>+ &minus; * /</code></dt><dd>the usual four</dd>
            <dt><code>TOTHE</code></dt><dd>raise to a power. <code>2 TOTHE .5</code> is a square root</dd>
            <dt><code>..</code></dt><dd>count from here to there. <code>1..9</code>, or backwards, <code>5..2</code></dd>
            <dt><code>MOD</code></dt><dd>what is left over</dd>
            <dt><code>MIN MAX</code></dt><dd>the lesser, the greater</dd>
            <dt><code>&lt; &gt; = &lt;= &gt;= &lt;&gt;</code></dt><dd>answer 1 for true, 0 for false</dd>
            <dt><code>AND OR XOR</code></dt><dd>for those who need them</dd>
            <dt><code>INSERT</code></dt><dd>put an operation between every element:
              <code>1..100 INSERT +</code></dd>
            <dt><code>:</code></dt><dd>store under a name. <code>5: fingers</code>. Nothing prints back</dd>
            <dt><code>[ ]</code></dt><dd>pick elements out by position, counting from 1</dd>
          </dl>
        </div>

        <div class="acl-refcol">
          <h3>After a thing</h3>
          <dl class="acl-defs">
            <dt><code>SIN COS TAN</code></dt><dd>and <code>ARCSIN ARCCOS ARCTAN</code></dd>
            <dt><code>LOG LN</code></dt><dd>base ten, and base e</dd>
            <dt><code>FLOOR CEILING</code></dt><dd>down to, up to</dd>
            <dt><code>ROUND TRUNCATE</code></dt><dd>to the nearest, or just chop</dd>
            <dt><code>NOT ODD EVEN</code></dt><dd>answering 1 or 0</dd>
            <dt><code>LENGTH</code></dt><dd>how many elements. <code>LEN</code> also works</dd>
            <dt><code>PICK</code></dt><dd>one at random out of the clump</dd>
            <dt><code>NUMBER LETTER</code></dt><dd>characters to their codes, and back</dd>
            <dt><code>STRING VALUE</code></dt><dd>a number to its digits, and back</dd>
          </dl>
          <h3>Names that mean something</h3>
          <dl class="acl-defs">
            <dt><code>PLACES</code></dt><dd>decimals shown. <code>7: PLACES</code></dd>
            <dt><code>RADIANS</code></dt><dd>1 for radians, 0 for degrees</dd>
            <dt><code>PI E</code></dt><dd>constants, and they stay that way</dd>
          </dl>
        </div>

        <div class="acl-refcol acl-refnote">
          <h3>Where this parts company with the primer</h3>
          <p><a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/bom/language.html" target="_blank" rel="noopener noreferrer external" data-wpel-link="external">Raskin&#8217;s manuscript</a> is a
            draft with a few worked answers that contradict its own
            rules. Every one of its 162 other examples runs here exactly as printed. These four
            do not, and the rule won:</p>
          <ul>
            <li><code>2..6 3..1</code> is printed as <code>2 3 4 5 6 3 2 1</code>, but the primer
              works through <code>1..4+4..1</code> three paragraphs earlier and gets a much longer
              clump. Left to right wins.</li>
            <li><code>0 1 0 1 OR (0 0 1 1)</code> is printed as <code>0 0 0 1</code>, which is
              what AND would say.</li>
            <li>100&deg;F is printed as <code>32.8</code>&deg;C. It is 37.8.</li>
            <li><code>5.1 17 _5.1 FLOOR</code> is printed as <code>5 15 _6</code>. The 17 does
              not become 15.</li>
          </ul>
          <p>Two places the primer leaves the answer to the reader are settled here as follows.
            <code>ROUND</code> sends a half to the even neighbour, which is the only rule that fits
            all five of its own examples. <code>NOT</code> is one minus the whole part, which is
            why the primer warns you off using it on anything but 0 and 1.</p>
        </div>
      </div>
    </details>

    <footer class="acl-foot">
      <h2 class="acl-foothead">Sources</h2>
      <ul class="acl-sources">
        <li>Jef Raskin, <a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/bom/language.html" target="_blank" rel="noopener noreferrer external" data-wpel-link="external"><em>The Apple
          Calculator Language Primer</em></a>, 13 October 1979. The document this page implements,
          in <em>The Macintosh Project: Selected Papers from Jef Raskin (First Macintosh Designer),
          Circa 1979</em>, document 14A, version 10.</li>
        <li>Alex Soojung-Kim Pang, <a href="https://web.stanford.edu/dept/SUL/sites/mac/index.html" target="_blank" rel="noopener noreferrer external" data-wpel-link="external"><em>Making
          the Macintosh: Technology and Culture in Silicon Valley</em></a>, Stanford University
          Libraries, the online archive that published the primer. Its <a href="https://web.stanford.edu/dept/SUL/sites/mac/primary/docs/index.html" target="_blank" rel="noopener noreferrer external" data-wpel-link="external">index of primary documents</a> holds the rest of
          Raskin&#8217;s papers from the same period.</li>
        <li>Apple Computer, Inc. Records, M1007, Series 3, Box 10, Folder 1, Department of Special
          Collections, Stanford University Libraries. <a href="https://archives.stanford.edu/catalog/m1007" target="_blank" rel="noopener noreferrer external" data-wpel-link="external">Finding aid</a> &middot; <a href="https://oac.cdlib.org/findaid/ark:/13030/tf4t1nb0n3" target="_blank" rel="noopener noreferrer external" data-wpel-link="external">Online Archive of California</a> &middot; <a href="https://searchworks.stanford.edu/view/4084835" target="_blank" rel="noopener noreferrer external" data-wpel-link="external">catalogue record</a>.</li>
      </ul>
    </footer>

  </div>
</div>

<style>
/* ===================================================================
   Everything is scoped under #acl so a theme cannot reach in
   and this cannot reach out.
   =================================================================== */
#acl{
  /* spirit duplicator on grey stock: black ribbon for the human,
     aniline purple for the machine, stamp red used exactly twice */
  --paper:#e9e9e2;
  --sheet:#f2f2ec;
  --ink:#262320;
  --ink-soft:#6c675e;
  --ditto:#43389b;
  --ditto-soft:#8981c0;
  --stamp:#a63428;
  --rule:#c3c1b6;
  --rule-soft:#d8d6cc;
  /* how far below the top of the window the panel parks itself. Raise this if
     your theme has a fixed header that would otherwise cover it. */
  --stick:20px;
  --chrome:0px;   /* set by the script when WordPress shows its admin bar */
  --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,Consolas,"DejaVu Sans Mono",monospace;
  --type:"Courier New",Courier,"Nimbus Mono PS","Liberation Mono",monospace;

  color-scheme:light;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--type);
  font-size:16px;
  line-height:1.5;
  padding:clamp(16px,3.4vw,36px) clamp(11px,4vw,40px);
  -webkit-text-size-adjust:100%;
}
#acl *{box-sizing:border-box;}
#acl p,#acl h1,#acl h2,#acl h3,#acl dl,#acl dd,#acl dt,#acl ul,#acl li{margin:0;padding:0;}
#acl code{font-family:var(--mono);font-size:.86em;background:none;padding:0;color:inherit;}
#acl em{font-style:italic;}
#acl a{color:var(--ditto);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px;}
#acl a:hover{color:var(--stamp);text-decoration-thickness:2px;}
#acl a:focus-visible{outline:2px solid var(--ditto);outline-offset:2px;}
#acl b{font-weight:700;}

#acl .acl-sheet{max-width:1120px;margin:0 auto;position:relative;}
#acl .acl-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;}

/* ---------- header ---------- */
#acl .acl-head{position:relative;padding-bottom:22px;margin-bottom:24px;border-bottom:2px solid var(--ink);}
#acl .acl-eyebrow{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:11px;}
#acl .acl-title{
  font-family:var(--type);font-weight:700;
  font-size:clamp(29px,5.6vw,50px);line-height:1.05;letter-spacing:-.015em;
  text-transform:uppercase;max-width:11ch;
}
#acl .acl-byline{margin-top:11px;font-size:13px;letter-spacing:.12em;text-transform:uppercase;color:var(--ditto);}
#acl .acl-lede{margin-top:15px;max-width:54ch;font-size:14.5px;line-height:1.6;color:var(--ink);}
#acl .acl-readmore{margin-top:11px;font-size:13px;letter-spacing:.02em;color:var(--ditto);}

#acl .acl-stamp{
  position:absolute;top:6px;right:0;
  display:none;flex-direction:column;gap:3px;
  border:2px solid var(--stamp);color:var(--stamp);
  padding:9px 13px;transform:rotate(-4deg);opacity:.62;
  font-size:9px;letter-spacing:.2em;text-transform:uppercase;text-align:center;line-height:1.3;
  border-radius:2px;text-decoration:none;
}
#acl .acl-stamp:hover{opacity:.92;color:var(--stamp);text-decoration:none;}
#acl .acl-stamp span:first-child{font-size:15px;font-weight:700;letter-spacing:.14em;}
#acl.acl-w760 .acl-stamp{display:flex;}

/* ---------- two column body ---------- */
#acl .acl-body{display:grid;grid-template-columns:1fr;gap:30px;align-items:start;}
#acl .acl-body>*{min-width:0;}   /* let both columns shrink on very narrow phones */
#acl.acl-w940 .acl-body{grid-template-columns:minmax(0,1.32fr) minmax(0,1fr);gap:38px;}

/* ---------- the machine ---------- */
#acl .acl-panelhead{
  font-size:11px;letter-spacing:.24em;text-transform:uppercase;font-weight:700;
  color:var(--ink-soft);margin-bottom:10px;
}
#acl .acl-panelhead-loose{margin-bottom:6px;}
#acl .acl-panel{background:var(--sheet);border:1px solid var(--rule);padding:16px 16px 14px;}
/* On a wide but short window the masthead gives up some of its height so the
   calculator still fits below it. */
#acl.acl-w940.acl-short{padding-top:22px;}
#acl.acl-w940.acl-short .acl-head{padding-bottom:18px;margin-bottom:20px;}
#acl.acl-w940.acl-short .acl-eyebrow{margin-bottom:9px;}
#acl.acl-w940.acl-short .acl-title{font-size:clamp(28px,3.6vw,40px);}
#acl.acl-w940.acl-short .acl-byline{margin-top:9px;font-size:12px;}
#acl.acl-w940.acl-short .acl-lede{margin-top:11px;font-size:13.5px;line-height:1.55;max-width:62ch;}
#acl.acl-w940.acl-short .acl-readmore{margin-top:8px;font-size:12.5px;}

/* On wide screens the panel parks itself at the top of the window and is given a
   measured height, so the tape takes whatever vertical room is left over. */
#acl.acl-w940 .acl-machine{position:sticky;top:calc(var(--stick) + var(--chrome));}
#acl.acl-w940 .acl-panel{display:flex;flex-direction:column;}
#acl.acl-w940 .acl-tape{flex:1 1 auto;height:auto;min-height:0;}

#acl .acl-tape{
  font-family:var(--mono);font-size:14px;line-height:1.68;
  height:min(58vh,460px);min-height:220px;overflow-y:auto;overflow-x:auto;
  padding:12px 14px;background:#fff;border:1px solid var(--rule-soft);
  white-space:pre-wrap;word-break:break-word;
}
#acl .acl-line{display:block;}
#acl .acl-said{color:var(--ink);}
#acl .acl-said::before{content:"! ";color:var(--ditto-soft);}
#acl .acl-back{color:var(--ditto);}
#acl .acl-oops{color:var(--stamp);font-weight:700;letter-spacing:.02em;}
#acl .acl-aside{color:var(--ink-soft);font-style:italic;}
#acl .acl-gap{display:block;height:.55em;}
#acl .acl-fresh{animation:acl-in .18s ease-out both;}
@keyframes acl-in{from{opacity:0;transform:translateY(3px);}to{opacity:1;transform:none;}}

#acl .acl-entry{display:flex;align-items:center;gap:0;margin-top:12px;border:1.5px solid var(--ink);background:#fff;}
#acl .acl-prompt{
  font-family:var(--mono);font-size:15px;color:var(--ditto);
  padding:0 2px 0 12px;transition:opacity .12s linear;user-select:none;
}
#acl .acl-prompt.acl-off{opacity:0;}
#acl .acl-in{
  flex:1;min-width:0;border:0;outline:0;background:transparent;
  font-family:var(--mono);font-size:15px;color:var(--ink);
  padding:11px 8px 11px 4px;
}
#acl .acl-in:focus{outline:0;}
#acl .acl-entry:focus-within{box-shadow:0 0 0 3px rgba(67,56,155,.22);}
#acl .acl-return{
  flex:none;border:0;border-left:1.5px solid var(--ink);background:var(--ink);color:var(--sheet);
  font-family:var(--type);font-size:11px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;
  padding:12px 15px;cursor:pointer;align-self:stretch;
}
#acl .acl-return:hover{background:var(--ditto);}
#acl .acl-return:focus-visible{outline:2px solid var(--ditto);outline-offset:2px;}

#acl .acl-status{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:12px;font-size:11px;}
#acl .acl-chip{
  letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft);
  border:1px solid var(--rule);padding:3px 8px;white-space:nowrap;
}
#acl .acl-chip i{font-style:normal;color:var(--ditto);font-weight:700;}
#acl .acl-chip-wide{max-width:100%;overflow:hidden;text-overflow:ellipsis;text-transform:none;letter-spacing:.06em;}
#acl .acl-chip-wide b{text-transform:uppercase;letter-spacing:.14em;}
#acl .acl-chip-wide i{font-family:var(--mono);font-size:11px;}
#acl .acl-spacer{flex:1;}
#acl .acl-mini{
  border:1px solid var(--rule);background:transparent;color:var(--ink-soft);cursor:pointer;
  font-family:var(--type);font-size:11px;letter-spacing:.12em;text-transform:uppercase;padding:3px 9px;
}
#acl .acl-mini:hover{border-color:var(--ink);color:var(--ink);}
#acl .acl-mini:focus-visible{outline:2px solid var(--ditto);outline-offset:2px;}

/* ---------- examples ---------- */
#acl .acl-hint{font-size:13px;color:var(--ink-soft);margin-bottom:18px;max-width:44ch;}
#acl .acl-group{margin-bottom:22px;}
#acl .acl-grouphead{
  font-size:10px;letter-spacing:.26em;text-transform:uppercase;font-weight:700;color:var(--ditto);
  padding-bottom:5px;border-bottom:1px solid var(--ditto-soft);margin-bottom:2px;
}
#acl .acl-card{
  display:block;width:100%;text-align:left;cursor:pointer;
  background:transparent;border:0;border-bottom:1px solid var(--rule-soft);
  padding:11px 4px 11px 0;font-family:var(--type);color:inherit;
}
#acl .acl-card:hover{background:rgba(67,56,155,.055);}
#acl .acl-card:focus-visible{outline:2px solid var(--ditto);outline-offset:-2px;}
#acl .acl-card-label{display:block;font-size:14px;font-weight:700;letter-spacing:.005em;}
#acl .acl-card-code{
  display:block;font-family:var(--mono);font-size:13px;color:var(--ditto);
  margin-top:5px;white-space:pre-wrap;word-break:break-word;line-height:1.5;
}
#acl .acl-card-quip{display:block;font-size:12.5px;color:var(--ink-soft);margin-top:5px;line-height:1.5;}

/* ---------- reference ---------- */
#acl .acl-ref{margin-top:34px;border-top:2px solid var(--ink);}
#acl .acl-ref summary{
  cursor:pointer;list-style:none;padding:15px 0 14px;
  font-size:12px;letter-spacing:.22em;text-transform:uppercase;font-weight:700;
}
#acl .acl-ref summary::-webkit-details-marker{display:none;}
#acl .acl-ref summary::before{content:"+ ";color:var(--ditto);}
#acl .acl-ref[open] summary::before{content:"\2212 ";}
#acl .acl-ref summary:focus-visible{outline:2px solid var(--ditto);outline-offset:3px;}
#acl .acl-refbody{display:grid;grid-template-columns:1fr;gap:26px;padding:6px 0 26px;}
#acl.acl-w700 .acl-refbody{grid-template-columns:repeat(2,minmax(0,1fr));}
#acl.acl-w1040 .acl-refbody{grid-template-columns:repeat(4,minmax(0,1fr));}
#acl .acl-refcol h3{
  font-size:10px;letter-spacing:.24em;text-transform:uppercase;color:var(--ditto);
  border-bottom:1px solid var(--ditto-soft);padding-bottom:5px;margin-bottom:11px;
}
#acl .acl-refcol h3+h3{margin-top:20px;}
#acl .acl-refcol p{font-size:13px;line-height:1.6;margin-bottom:10px;}
#acl .acl-defs dt{font-family:var(--mono);font-size:12.5px;color:var(--ditto);margin-top:9px;}
#acl .acl-defs dd{font-size:12.5px;line-height:1.5;color:var(--ink);}
#acl .acl-refnote ul{margin:0 0 10px;padding-left:18px;}
#acl .acl-refnote li{font-size:12.5px;line-height:1.55;margin-bottom:7px;}
#acl .acl-refnote code{color:var(--ditto);}

#acl .acl-foot{border-top:1px solid var(--rule);padding-top:14px;}
#acl .acl-foothead{font-size:10px;letter-spacing:.24em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:9px;}
#acl .acl-sources{list-style:none;max-width:82ch;}
#acl .acl-sources li{font-size:11.5px;line-height:1.65;color:var(--ink-soft);margin-bottom:7px;padding-left:14px;position:relative;}
#acl .acl-sources li::before{content:"\2014";position:absolute;left:0;color:var(--rule);}

@media(prefers-reduced-motion:reduce){
  #acl .acl-fresh{animation:none;}
  #acl *{transition:none !important;}
}
</style>

<script>
<!--
(function(){
  "use strict";

  /* ===================================================================
     THE APPLE CALCULATOR LANGUAGE  :  interpreter
     after Jef Raskin, 13 October 1979
     =================================================================== */

  function AppleError(msg){ this.name="AppleError"; this.message=msg; }
  AppleError.prototype = Object.create(Error.prototype);
  function oops(msg){ throw new AppleError(msg); }

  var DYADIC_WORDS  = ["TOTHE","MOD","MIN","MAX","AND","OR","XOR"];
  var MONADIC_WORDS = ["ARCSIN","ARCCOS","ARCTAN","SIN","COS","TAN","NOT","LOG","LN",
                       "FLOOR","CEILING","ROUND","TRUNCATE","PICK","LENGTH","LEN",
                       "ODD","EVEN","NUMBER","LETTER","STRING","VALUE"];
  var SPECIAL_WORDS = ["PLACES","RADIANS","PI","E"];
  var ALL_KEYWORDS  = DYADIC_WORDS.concat(MONADIC_WORDS, SPECIAL_WORDS, ["INSERT"]);
  var SYMBOL_OPS    = ["..","<=",">=","<>","+","-","*","/","<",">","="];

  /* padding element when clumps of unequal length meet, per Chapter 7 */
  var PAD = { "+":0,"-":0,"*":1,"/":1,"TOTHE":1,"..":1,"MOD":0,"MIN":0,"MAX":0,
              "<":0,"=":0,">":0,">=":0,"<=":0,"<>":0,"AND":1,"OR":0,"XOR":0 };

  /* a value is { items:[ {t:"n",v:Number} | {t:"c",v:"x"} ], unset:Boolean } */
  function num(v){ return { items:[{t:"n",v:v}] }; }
  function chars(s){ var o=[],i; for(i=0;i<s.length;i++) o.push({t:"c",v:s.charAt(i)}); return {items:o}; }
  function clump(items){ return { items:items }; }
  function hasChars(items){ for(var i=0;i<items.length;i++) if(items[i].t==="c") return true; return false; }
  function asNumber(it){ return it.t==="n" ? it.v : it.v.charCodeAt(0); }

  function concat(a,b){
    if(!a) return b;
    if(a.unset && hasChars(b.items)) return clump(b.items.slice());
    if(b.unset && hasChars(a.items)) return clump(a.items.slice());
    return clump(a.items.concat(b.items));
  }

  function roundHalfEven(v){
    var f=Math.floor(v), d=v-f;
    if(d>0.5) return f+1;
    if(d<0.5) return f;
    return (f%2===0)? f : f+1;
  }

  function fmtNum(v,places){
    if(typeof v!=="number" || v!==v) return "NOT A NUMBER";
    if(v===Infinity)  return "INFINITY";
    if(v===-Infinity) return "_INFINITY";
    var neg=v<0, a=Math.abs(v), s;
    /* small values just round away at the current PLACES, the way a pocket
       calculator does: the primer expects 3.141592654 SIN to show 0 */
    if(a>=1e15){
      s=a.toExponential(Math.max(1,Math.min(places,15)));
      s=s.replace(/0+e/,"e").replace(/\.e/,"e");
      s=s.replace("e+","E").replace("e-","E_");
    }else{
      s=Number(a.toPrecision(15)).toFixed(Math.min(places,20));
      if(s.indexOf(".")>=0) s=s.replace(/0+$/,"").replace(/\.$/,"");
      if(s==="") s="0";
    }
    if(neg && parseFloat(s)!==0) s="_"+s;
    return s;
  }

  function render(value,places){
    var out="",prev=null,i,it;
    for(i=0;i<value.items.length;i++){
      it=value.items[i];
      if(prev!==null && (it.t==="n" || prev==="n")) out+=" ";
      out += (it.t==="c") ? it.v : fmtNum(it.v,places);
      prev=it.t;
    }
    return out;
  }

  /* ---------- tokenizer ---------- */
  function isDigit(c){ return c>="0" && c<="9"; }
  function isLetter(c){ return (c>="a"&&c<="z")||(c>="A"&&c<="Z"); }
  function isNameChar(c){ return isLetter(c)||isDigit(c); }

  function scanNumber(src,i){
    var start=i,seenDot=false,digits=0,c;
    while(i<src.length){
      c=src.charAt(i);
      if(isDigit(c)){ digits++; i++; continue; }
      if(c==="." && !seenDot){
        if(src.charAt(i+1)===".") break;      /* that is the range operator */
        seenDot=true; i++; continue;
      }
      break;
    }
    if(digits===0) return null;
    return { text:src.slice(start,i), end:i };
  }

  function scanName(src,i){
    var start=i,c,nxt;
    if(!isLetter(src.charAt(i))) return null;
    i++;
    while(i<src.length){
      c=src.charAt(i);
      if(isNameChar(c)){ i++; continue; }
      nxt=src.charAt(i+1);
      if((c==="."||c==="_") && isNameChar(nxt)){ i++; continue; }
      break;
    }
    return { text:src.slice(start,i), end:i };
  }

  function tokenize(src){
    var toks=[],i=0,n=src.length,c,m,q,s,depth,k,sym,found;
    while(i<n){
      c=src.charAt(i);
      if(c===" "||c==="\t"||c==="\r"||c==="\n"){ i++; continue; }

      /* braces hold the primer's own marginalia; here they are comments */
      if(c==="{"){
        depth=1; i++;
        while(i<n && depth>0){
          if(src.charAt(i)==="{") depth++;
          else if(src.charAt(i)==="}") depth--;
          i++;
        }
        continue;
      }
      if(c==="}") oops('THERE IS A "}" WITH NO "{" IN FRONT OF IT');

      if(c==='"' || c==="'"){
        q=c; i++; s="";
        while(i<n && src.charAt(i)!==q){ s+=src.charAt(i); i++; }
        if(i>=n) oops("THAT QUOTATION IS NEVER CLOSED");
        i++;
        toks.push({k:"str",v:s});
        continue;
      }

      if(c==="_"){
        m=scanNumber(src,i+1);
        if(!m) oops("AN UNDERSCORE MUST BE FOLLOWED BY A NUMBER, AS IN _45.4");
        toks.push({k:"num",v:-parseFloat(m.text)});
        i=m.end;
        continue;
      }

      if(isDigit(c) || (c==="." && isDigit(src.charAt(i+1)))){
        m=scanNumber(src,i);
        if(m){ toks.push({k:"num",v:parseFloat(m.text)}); i=m.end; continue; }
      }

      if(isLetter(c)){
        m=scanName(src,i);
        i=m.end;
        if(m.text==="INSERT"){ toks.push({k:"insert"}); continue; }
        if(DYADIC_WORDS.indexOf(m.text)>=0){ toks.push({k:"op",v:m.text}); continue; }
        if(MONADIC_WORDS.indexOf(m.text)>=0){ toks.push({k:"mon",v:m.text}); continue; }
        if(SPECIAL_WORDS.indexOf(m.text)>=0){ toks.push({k:"special",v:m.text}); continue; }
        if(m.text.length>1 && m.text===m.text.toUpperCase() && /[A-Z]/.test(m.text)){
          oops('I DO NOT KNOW THE WORD "'+m.text+'"');
        }
        toks.push({k:"name",v:m.text});
        continue;
      }

      found=false;
      for(k=0;k<SYMBOL_OPS.length;k++){
        sym=SYMBOL_OPS[k];
        if(src.substr(i,sym.length)===sym){
          toks.push({k:"op",v:sym});
          i+=sym.length; found=true; break;
        }
      }
      if(found) continue;

      if(c===":"){ toks.push({k:"colon"}); i++; continue; }
      if("()[]".indexOf(c)>=0){ toks.push({k:c}); i++; continue; }

      oops('I CANNOT MAKE SENSE OF "'+c+'"');
    }
    return toks;
  }

  /* ---------- the machine ---------- */
  function Machine(){ this.reset(); }
  Machine.prototype.reset=function(){ this.vars={}; this.places=2; this.radians=1; };

  Machine.prototype.lookup=function(name){
    if(Object.prototype.hasOwnProperty.call(this.vars,name)){
      return clump(this.vars[name].items.slice());
    }
    return { items:[{t:"n",v:0}], unset:true };
  };

  Machine.prototype.special=function(word){
    if(word==="PI") return num(Math.PI);
    if(word==="E")  return num(Math.E);
    if(word==="PLACES") return num(this.places);
    return num(this.radians);
  };

  function pairOp(op,x,y,m){
    var a=asNumber(x),b=asNumber(y),lo,hi,out,i,t;
    switch(op){
      case "+": return [{t:"n",v:a+b}];
      case "-": return [{t:"n",v:a-b}];
      case "*": return [{t:"n",v:a*b}];
      case "/":
        if(b===0) oops("YOU MUSTN'T DIVIDE BY ZERO");
        return [{t:"n",v:a/b}];
      case "MOD":
        if(b===0) oops("YOU MUSTN'T DIVIDE BY ZERO");
        return [{t:"n",v:a-b*Math.floor(a/b)}];
      case "TOTHE":
        t=Math.pow(a,b);
        if(t!==t) oops("I CANNOT RAISE "+fmtNum(a,m.places)+" TO THE POWER "+fmtNum(b,m.places));
        return [{t:"n",v:t}];
      case "MIN": return [{t:"n",v:Math.min(a,b)}];
      case "MAX": return [{t:"n",v:Math.max(a,b)}];
      case "<":   return [{t:"n",v:a<b?1:0}];
      case ">":   return [{t:"n",v:a>b?1:0}];
      case "=":   return [{t:"n",v:a===b?1:0}];
      case "<=":  return [{t:"n",v:a<=b?1:0}];
      case ">=":  return [{t:"n",v:a>=b?1:0}];
      case "<>":  return [{t:"n",v:a!==b?1:0}];
      case "AND": return [{t:"n",v:(a|0)&(b|0)}];
      case "OR":  return [{t:"n",v:(a|0)|(b|0)}];
      case "XOR": return [{t:"n",v:(a|0)^(b|0)}];
      case "..":
        lo=Math.trunc(a); hi=Math.trunc(b);
        if(Math.abs(hi-lo)>20000) oops("THAT RANGE IS TOO LONG FOR ME TO HOLD");
        out=[];
        if(lo<=hi){ for(i=lo;i<=hi;i++) out.push({t:"n",v:i}); }
        else      { for(i=lo;i>=hi;i--) out.push({t:"n",v:i}); }
        return out;
      default:
        oops('I DO NOT KNOW THE OPERATION "'+op+'"');
    }
  }

  function padItem(op,sample){
    /* "Strings are padded with nulls." Numbers take the operation's identity. */
    if(sample && sample.t==="c") return {t:"c",v:"\u0000"};
    var p=PAD[op];
    return {t:"n",v:(p===undefined?0:p)};
  }

  function dyad(op,A,B,m){
    var a=A.items,b=B.items,la=a.length,lb=b.length,pa=a,pb=b,L,out=[],i,r;
    if(la===0 && lb===0) return clump([]);
    if(la===0){ pa=[padItem(op,b[0])]; la=1; }
    if(lb===0){ pb=[padItem(op,a[0])]; lb=1; }

    if(la===1 && lb>1){ pa=[]; for(i=0;i<lb;i++) pa.push(a[0]); }
    else if(lb===1 && la>1){ pb=[]; for(i=0;i<la;i++) pb.push(b[0]); }
    else if(la!==lb){
      L=Math.max(la,lb);
      pa=pa.slice(); pb=pb.slice();
      while(pa.length<L) pa.push(padItem(op,a[a.length-1]));
      while(pb.length<L) pb.push(padItem(op,b[b.length-1]));
    }
    for(i=0;i<pa.length;i++){
      r=pairOp(op,pa[i],pb[i],m);
      out.push.apply(out,r);
    }
    return clump(out);
  }

  function toRad(v,m){ return m.radians ? v : v*Math.PI/180; }
  function fromRad(v,m){ return m.radians ? v : v*180/Math.PI; }

  function monad(word,V,m){
    var items=V.items,out=[],i,x,v,code,txt;

    if(word==="LENGTH"||word==="LEN") return num(items.length);
    if(word==="PICK"){
      if(!items.length) oops("THERE IS NOTHING HERE TO PICK FROM");
      return clump([items[Math.floor(Math.random()*items.length)]]);
    }
    if(word==="VALUE"){
      txt="";
      for(i=0;i<items.length;i++){
        txt += items[i].t==="c" ? items[i].v : fmtNum(items[i].v,m.places);
      }
      txt=txt.replace(/_/g,"-").trim();
      v=parseFloat(txt);
      return num(v!==v?0:v);
    }
    if(word==="NUMBER" && items.length===0) return num(0);

    for(i=0;i<items.length;i++){
      x=items[i];
      switch(word){
        case "SIN": out.push({t:"n",v:Math.sin(toRad(asNumber(x),m))}); break;
        case "COS": out.push({t:"n",v:Math.cos(toRad(asNumber(x),m))}); break;
        case "TAN": out.push({t:"n",v:Math.tan(toRad(asNumber(x),m))}); break;
        case "ARCSIN":
          v=asNumber(x);
          if(v<-1||v>1) oops("ARCSIN ONLY LIKES NUMBERS FROM _1 TO 1");
          out.push({t:"n",v:fromRad(Math.asin(v),m)}); break;
        case "ARCCOS":
          v=asNumber(x);
          if(v<-1||v>1) oops("ARCCOS ONLY LIKES NUMBERS FROM _1 TO 1");
          out.push({t:"n",v:fromRad(Math.acos(v),m)}); break;
        case "ARCTAN": out.push({t:"n",v:fromRad(Math.atan(asNumber(x)),m)}); break;
        case "LOG":
          v=asNumber(x);
          if(v<=0) oops("I CANNOT TAKE THE LOGARITHM OF A NUMBER THAT SMALL");
          out.push({t:"n",v:Math.log(v)/Math.LN10}); break;
        case "LN":
          v=asNumber(x);
          if(v<=0) oops("I CANNOT TAKE THE LOGARITHM OF A NUMBER THAT SMALL");
          out.push({t:"n",v:Math.log(v)}); break;
        case "NOT":      out.push({t:"n",v:1-Math.trunc(asNumber(x))}); break;
        case "FLOOR":    out.push({t:"n",v:Math.floor(asNumber(x))}); break;
        case "CEILING":  out.push({t:"n",v:Math.ceil(asNumber(x))}); break;
        case "ROUND":    out.push({t:"n",v:roundHalfEven(asNumber(x))}); break;
        case "TRUNCATE": out.push({t:"n",v:Math.trunc(asNumber(x))}); break;
        case "ODD":      out.push({t:"n",v:Math.abs(Math.trunc(asNumber(x)))%2===1?1:0}); break;
        case "EVEN":     out.push({t:"n",v:Math.abs(Math.trunc(asNumber(x)))%2===0?1:0}); break;
        case "NUMBER":   out.push({t:"n",v:asNumber(x)}); break;
        case "LETTER":
          code=Math.trunc(asNumber(x));
          /* out of range gives back the null character, which shows as nothing */
          if(code>0 && code<1114112) out.push({t:"c",v:String.fromCodePoint(code)});
          break;
        case "STRING":
          if(x.t==="c"){ out.push(x); }
          else{
            txt=fmtNum(x.v,m.places);
            for(v=0;v<txt.length;v++) out.push({t:"c",v:txt.charAt(v)});
          }
          break;
        default:
          oops('I DO NOT KNOW THE OPERATION "'+word+'"');
      }
    }
    return clump(out);
  }

  /* ---------- evaluator: strictly left to right ---------- */
  function describe(tok){
    if(!tok) return "THE END OF THE LINE";
    if(tok.k==="num") return String(tok.v);
    if(tok.k==="str") return '"'+tok.v+'"';
    if(tok.k==="colon") return ":";
    if(tok.k==="insert") return "INSERT";
    if(tok.v!==undefined) return String(tok.v);
    return tok.k;
  }

  var STARTERS={ "num":1,"str":1,"name":1,"special":1,"(":1 };

  function Parser(toks,m){ this.t=toks; this.i=0; this.m=m; }
  Parser.prototype.peek=function(){ return this.t[this.i]||null; };
  Parser.prototype.next=function(){ return this.t[this.i++]||null; };

  Parser.prototype.element=function(why){
    var tok=this.next(),v;
    if(!tok) oops('I EXPECTED SOMETHING AFTER "'+why+'" BUT THE LINE ENDED');
    switch(tok.k){
      case "num":     return num(tok.v);
      case "str":     return chars(tok.v);
      case "name":    return this.m.lookup(tok.v);
      case "special": return this.m.special(tok.v);
      case "(":
        v=this.expression();
        if(!this.peek() || this.peek().k!==")") oops("A PARENTHESIS IS NEVER CLOSED");
        this.next();
        return v.value;
      default:
        oops('I EXPECTED A NUMBER OR A NAME AFTER "'+why+'", NOT "'+describe(tok)+'"');
    }
  };

  Parser.prototype.expression=function(){
    var acc=null,tok,bareSpecial=null,assigned=false,rhs,idx,opTok,val,after,m=this.m;

    for(;;){
      tok=this.peek();
      if(!tok || tok.k===")" || tok.k==="]") break;

      if(STARTERS[tok.k]){
        bareSpecial=(acc===null && tok.k==="special" &&
                     (tok.v==="PLACES"||tok.v==="RADIANS")) ? tok.v : null;
        val=this.element("the start of the line");
        acc=concat(acc,val);
        assigned=false;
        continue;
      }

      if(tok.k==="["){
        if(acc===null) oops('A "[" NEEDS A CLUMP TO ITS LEFT');
        this.next();
        idx=this.expression();
        if(!this.peek() || this.peek().k!=="]") oops("A BRACKET IS NEVER CLOSED");
        this.next();
        acc=select(acc,idx.value);
        bareSpecial=null; assigned=false;
        continue;
      }

      if(tok.k==="mon"){
        if(acc===null) oops(tok.v+" WANTS A CLUMP TO ITS LEFT");
        this.next();
        acc=monad(tok.v,acc,m);
        bareSpecial=null; assigned=false;
        continue;
      }

      if(tok.k==="insert"){
        if(acc===null) oops("INSERT WANTS A CLUMP TO ITS LEFT");
        this.next();
        opTok=this.next();
        if(!opTok || opTok.k!=="op") oops("INSERT MUST BE FOLLOWED BY AN OPERATION LIKE + OR MAX");
        acc=fold(opTok.v,acc,m);
        bareSpecial=null; assigned=false;
        continue;
      }

      if(tok.k==="colon"){
        this.next();
        after=this.peek();
        if(after && (after.k==="name"||after.k==="special")){
          this.next();
          assign(m,after.v,acc,after.k==="special");
          assigned=true; bareSpecial=null;
          continue;
        }
        if(after && (after.k==="mon"||after.k==="insert"||
                    (after.k==="op" && /^[A-Z]+$/.test(String(after.v))))){
          oops("YOU MAY NOT USE THE KEYWORD "+describe(after)+" AS A NAME");
        }
        if(bareSpecial){                 /* the primer also writes  PLACES: 3 */
          val=this.element(":");
          assign(m,bareSpecial,val,true);
          acc=val; assigned=true; bareSpecial=null;
          continue;
        }
        oops("THE COLON WANTS A NAME ON ITS RIGHT, AS IN  5: fingers");
      }

      if(tok.k==="op"){
        if(acc===null) oops('I EXPECTED SOMETHING BEFORE "'+tok.v+'"');
        this.next();
        rhs=this.element(tok.v);
        acc=dyad(tok.v,acc,rhs,m);
        bareSpecial=null; assigned=false;
        continue;
      }

      oops('I CANNOT MAKE SENSE OF "'+describe(tok)+'"');
    }

    if(acc===null) return { value:clump([]), assigned:false, empty:true };
    return { value:acc, assigned:assigned, empty:false };
  };

  function assign(m,name,value,isSpecial){
    var v;
    if(!value) oops("THERE IS NOTHING TO THE LEFT OF THE COLON TO STORE");
    if(isSpecial || SPECIAL_WORDS.indexOf(name)>=0){
      if(name==="PI"||name==="E") oops("PI AND E ARE CONSTANTS AND CANNOT BE CHANGED");
      v=value.items.length ? asNumber(value.items[0]) : 0;
      if(name==="PLACES"){
        v=Math.trunc(v);
        if(v<0||v>18) oops("PLACES MUST BE A WHOLE NUMBER FROM 0 TO 18");
        m.places=v;
      }else{
        m.radians = v ? 1 : 0;
      }
      return;
    }
    if(ALL_KEYWORDS.indexOf(name)>=0) oops("YOU MAY NOT USE THE KEYWORD "+name+" AS A NAME");
    m.vars[name]=clump(value.items.slice());
  }

  function fold(op,V,m){
    var items=V.items,acc,i;
    if(!items.length) return num(PAD[op]===undefined?0:PAD[op]);
    acc=clump([items[0]]);
    for(i=1;i<items.length;i++) acc=dyad(op,acc,clump([items[i]]),m);
    return acc;
  }

  function select(source,index){
    var out=[],i,k,isText=hasChars(source.items);
    for(i=0;i<index.items.length;i++){
      k=Math.trunc(asNumber(index.items[i]));
      if(k>=1 && k<=source.items.length) out.push(source.items[k-1]);
      else if(!isText) out.push({t:"n",v:0});
      /* a character clump out of range gives the null string: nothing at all */
    }
    return clump(out);
  }

  Machine.prototype.run=function(line){
    var toks,p,res;
    try{
      toks=tokenize(line);
      if(!toks.length) return { kind:"silent" };
      p=new Parser(toks,this);
      res=p.expression();
      if(p.peek()) oops('I DO NOT KNOW WHAT "'+describe(p.peek())+'" IS DOING THERE');
      if(res.assigned || res.empty) return { kind:"silent" };
      return { kind:"answer", text:render(res.value,this.places) };
    }catch(e){
      if(e instanceof AppleError) return { kind:"error", text:e.message };
      if(e && /call stack|too much recursion/i.test(e.message||"")){
        return { kind:"error", text:"THAT EXPRESSION IS NESTED DEEPER THAN I CAN FOLLOW" };
      }
      return { kind:"error", text:"SOMETHING WENT WRONG: "+((e&&e.message)||e) };
    }
  };

  /* ===================================================================
     STARTING POINTS
     =================================================================== */
  var GROUPS = [
    { name:"First principles", items:[
      { label:"Chain calculation, 1979",
        code:["6/3+2*5"],
        quip:"Six, over three, plus two, times five. No precedence, no exceptions." },
      { label:"The answer isn't 13",
        code:["55/11+1+12/6*2-3"],
        quip:"Raskin's own trick question. It isn't 7 either." },
      { label:"Parentheses, if you insist",
        code:["(6/3)+(2*5)"],
        quip:"The only way to make a piece of the line go first." },
      { label:"Golden ratio, no parentheses needed",
        code:["5 TOTHE .5 + 1 / 2"],
        quip:"Root five, plus one, halved. Left to right happens to spell it perfectly." }
    ]},
    { name:"Clumps", items:[
      { label:"The thirteen times table",
        code:["1..9 *13"],
        quip:"Two dots count from here to there. One operation does all nine." },
      { label:"Fahrenheit, meet Celsius",
        code:["1: PLACES","32 50 100 212 -32*5/9"],
        quip:"Four temperatures walk through three operations together." },
      { label:"A year, itemised",
        code:["31 28 31 30 31 30 31 31 30 31 30 31 INSERT +"],
        quip:"INSERT slides a + between every pair and totals the lot." },
      { label:"Gauss's homework",
        code:["1..100 INSERT +"],
        quip:"He was seven and did it in his head. You have a browser." },
      { label:"The left to right trap",
        code:["1 2 3 4+4 3 2 1"],
        quip:"You expected 5 5 5 5. The + only ever takes one thing from its right." },
      { label:"Pi wanders into a range",
        code:["PI * 1..4"],
        quip:"Ranges want whole numbers, so pi is quietly chopped to 3. Dignity lost." },
      { label:"Powers of two, to a familiar one",
        code:["2 TOTHE (1..16)"],
        quip:"Ending, as these things do, at 65536." },
      { label:"Ten years at five percent",
        code:["2: PLACES","1000 * (1.05 TOTHE (1..10))"],
        quip:"A whole compounding table in nineteen characters." }
    ]},
    { name:"Words", items:[
      { label:"Haddock smells",
        code:['"had" "dock" " smells"'],
        quip:"Raskin's example, verbatim. We have chosen not to ask." },
      { label:"Stressed, reconsidered",
        code:['"stressed" [8..1]'],
        quip:"Brackets pick letters by position. Count backwards and dinner is served." },
      { label:"Portia counts her E's",
        code:['"The quality of mercy is not strained." = "e" INSERT +'],
        quip:"Compare every letter to e, then add up the trues." },
      { label:"Secret decoder ring",
        code:['"attack at dawn" NUMBER +3 LETTER'],
        quip:"Letters to codes, shift by three, codes back to letters." },
      { label:"The ring, run backwards",
        code:['"dwwdfn#dw#gdzq" NUMBER -3 LETTER'],
        quip:"Caesar would have wanted this." },
      { label:"Losing your indoor voice",
        code:['"indoor voice" NUMBER -32 LETTER'],
        quip:"Thirty two below a lower case letter is the capital. It also eats the space, which becomes the null character." },
      { label:"Is it a palindrome?",
        code:['"racecar" = ("racecar" [7..1]) INSERT AND'],
        quip:"1 for yes. Swap in kayaks and watch it say 0." },
      { label:"A cat has four legs",
        code:['"cat" LENGTH'],
        quip:"The word has none, and three letters. Raskin's line, not ours." }
    ]},
    { name:"Parlour tricks", items:[
      { label:"Roll two dice",
        code:["(1..6 PICK) + (1..6 PICK)"],
        quip:"PICK takes one at random out of the clump. Press it again." },
      { label:"Name your fingers",
        code:["5: fingers","fingers * 2"],
        quip:"Storing something prints nothing back. Then you ask for it." },
      { label:"Count the odd ones out",
        code:["12 34 55 18 67 31 24 ODD INSERT +"],
        quip:"ODD marks each one 1 or 0, and INSERT + counts the 1s." },
      { label:"Multiples of fifteen under a hundred",
        code:["1..100 MOD 15 = 0 INSERT +"],
        quip:"The same trick, one clump longer." },
      { label:"Do not press this",
        code:["3/0"],
        quip:"The primer's one and only error message, reproduced faithfully." },
      { label:"The averaging program, entire",
        code:["3 1 4 1 5 9 2 6: many.numbers","many.numbers INSERT + / (many.numbers LENGTH)"],
        quip:"Raskin's Rosetta Stone entry. The FORTRAN version needed five times the characters." }
    ]}
  ];

  /* ===================================================================
     THE PAGE
     =================================================================== */
  var root    = document.getElementById("acl");
  var panel   = document.querySelector("#acl .acl-panel");
  var column  = document.querySelector("#acl .acl-machine");
  var bodyEl  = document.querySelector("#acl .acl-body");
  var tape    = document.getElementById("acl-tape");
  var input   = document.getElementById("acl-in");
  var prompt  = document.getElementById("acl-prompt");
  var machine = new Machine();
  var history = [], histAt = 0;
  var animate = !(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);

  function line(cls,text){
    var el=document.createElement("span");
    el.className="acl-line "+cls+(animate?" acl-fresh":"");
    el.textContent=text;
    tape.appendChild(el);
    tape.scrollTop=tape.scrollHeight;
    return el;
  }
  function gap(){
    var el=document.createElement("span");
    el.className="acl-gap";
    tape.appendChild(el);
  }

  function refreshStatus(){
    document.getElementById("acl-places").textContent  = machine.places;
    document.getElementById("acl-radians").textContent = machine.radians;
    var names=Object.keys(machine.vars);
    document.getElementById("acl-names").textContent =
      names.length ? names.join(", ") : "nothing yet";
  }

  function runLine(text){
    if(!text.replace(/\s/g,"")) return;
    line("acl-said",text);
    var res=machine.run(text);
    if(res.kind==="answer") line("acl-back",res.text);
    else if(res.kind==="error") line("acl-oops",res.text);
    gap();
    refreshStatus();
  }

  function submit(){
    var text=input.value;
    if(!text.replace(/\s/g,"")) return;
    history.push(text);
    if(history.length>200) history.shift();
    histAt=history.length;
    runLine(text);
    input.value="";
    prompt.classList.remove("acl-off");
  }

  input.addEventListener("keydown",function(e){
    if(e.key==="Enter"){ e.preventDefault(); submit(); return; }
    if(e.key==="ArrowUp"){
      if(!history.length) return;
      e.preventDefault();
      histAt=Math.max(0,histAt-1);
      input.value=history[histAt]||"";
      prompt.classList.toggle("acl-off",!!input.value);
      return;
    }
    if(e.key==="ArrowDown"){
      if(!history.length) return;
      e.preventDefault();
      histAt=Math.min(history.length,histAt+1);
      input.value=history[histAt]||"";
      prompt.classList.toggle("acl-off",!!input.value);
    }
  });

  /* the primer: "the first character that you type replaces the !" */
  input.addEventListener("input",function(){
    prompt.classList.toggle("acl-off",input.value.length>0);
  });

  document.getElementById("acl-return").addEventListener("click",function(){
    submit(); input.focus();
  });

  document.getElementById("acl-clear").addEventListener("click",function(){
    while(tape.firstChild) tape.removeChild(tape.firstChild);
    greet();
    input.focus();
  });

  document.getElementById("acl-forget").addEventListener("click",function(){
    machine.reset();
    refreshStatus();
    line("acl-aside","Every name forgotten. PLACES back to 2, RADIANS back to 1.");
    gap();
    input.focus();
  });

  /* build the example cards */
  (function buildCards(){
    var host=document.getElementById("acl-cards"),g,i,j,grp,ex,btn,lab,code,quip;
    for(i=0;i<GROUPS.length;i++){
      grp=GROUPS[i];
      g=document.createElement("div");
      g.className="acl-group";
      var h=document.createElement("h3");
      h.className="acl-grouphead";
      h.textContent=grp.name;
      g.appendChild(h);

      for(j=0;j<grp.items.length;j++){
        ex=grp.items[j];
        btn=document.createElement("button");
        btn.type="button";
        btn.className="acl-card";

        lab=document.createElement("span");
        lab.className="acl-card-label";
        lab.textContent=ex.label;

        code=document.createElement("span");
        code.className="acl-card-code";
        code.textContent=ex.code.join("\n");

        quip=document.createElement("span");
        quip.className="acl-card-quip";
        quip.textContent=ex.quip;

        btn.appendChild(lab); btn.appendChild(code); btn.appendChild(quip);
        btn.addEventListener("click",(function(lines){
          return function(){
            for(var k=0;k<lines.length;k++) runLine(lines[k]);
            input.value=lines[lines.length-1];
            prompt.classList.add("acl-off");
            history.push(lines[lines.length-1]);
            histAt=history.length;
            input.focus();
            input.setSelectionRange(input.value.length,input.value.length);
          };
        })(ex.code));

        g.appendChild(btn);
      }
      host.appendChild(g);
    }
  })();

  /* The tape opens mid session, the way you would find a machine someone
     had been using. These run through the interpreter for real. */
  function greet(){
    line("acl-aside","Type an expression and press Return. Notes to yourself go in {braces}.");
    gap();
    runLine("6/3+2*5");
    runLine("1..9 *13");
    runLine('"stressed" [8..1]');
  }

  /* ---------------------------------------------------------------
     Layout.

     Two things here are measured by hand rather than left to CSS media
     queries, because this page is meant to be dropped inside somebody
     else's template.

     1. The breakpoints watch the width of #acl itself, not the width of
        the window. A post column is often far narrower than the screen,
        and a media query cannot tell the difference.

     2. The panel's height comes from where it parks, not from where it
        happens to sit in the document. On a page of its own those are
        the same number. In a post, with a site header, a title, a byline
        and a featured image above it, they are not, and measuring from
        the document top leaves the tape stuck at its minimum.
     --------------------------------------------------------------- */
  var W_STAMP = 760, W_TWOCOL = 940, W_REF2 = 700, W_REF4 = 1040;
  var FLOOR = 300;      /* never squeeze the panel smaller than this */
  var HEM   = 22;       /* breathing room at the bottom of the window */
  var lastHeight = false;

  /* WordPress hangs a fixed admin bar over the top of the page for logged
     in users. Park below it rather than underneath it. */
  function chromeAtTop(){
    var bar = document.getElementById("wpadminbar"), pos;
    if(!bar) return 0;
    pos = window.getComputedStyle(bar).position;
    if(pos !== "fixed" && pos !== "sticky") return 0;
    return bar.offsetHeight || 0;
  }

  function stickOffset(){
    var v = parseFloat(window.getComputedStyle(column).top);
    return (v === v) ? v : 0;
  }

  function targetHeight(){
    if(!root.classList.contains("acl-w940")) return null;
    var vh = window.innerHeight, stick = stickOffset();
    /* the height it needs once parked, wherever it sits in the page */
    var room = vh - stick - HEM;
    /* unless the whole thing already starts inside the first screenful, in
       which case leave the masthead its room so nothing is cut off before
       the reader has scrolled at all */
    var docTop = bodyEl.getBoundingClientRect().top + (window.pageYOffset || 0);
    if(docTop > stick && docTop < vh) room = vh - docTop - HEM;
    return Math.max(FLOOR, Math.round(room));
  }

  function layout(){
    var w = root.clientWidth, h;
    root.classList.toggle("acl-w700",  w >= W_REF2);
    root.classList.toggle("acl-w760",  w >= W_STAMP);
    root.classList.toggle("acl-w940",  w >= W_TWOCOL);
    root.classList.toggle("acl-w1040", w >= W_REF4);
    root.classList.toggle("acl-short", window.innerHeight <= 1000);
    root.style.setProperty("--chrome", chromeAtTop() + "px");

    h = targetHeight();
    if(h !== lastHeight){                 /* only write when it really changed,
                                             so the observer cannot loop */
      panel.style.height = (h === null) ? "" : h + "px";
      lastHeight = h;
      tape.scrollTop = tape.scrollHeight;
    }
  }

  var pending = 0;
  function scheduleLayout(){
    if(pending) return;
    pending = window.requestAnimationFrame(function(){ pending = 0; layout(); });
  }
  window.addEventListener("resize", scheduleLayout);
  window.addEventListener("orientationchange", scheduleLayout);
  window.addEventListener("load", scheduleLayout);
  if(window.ResizeObserver) new ResizeObserver(scheduleLayout).observe(root);

  /* position:sticky quietly stops working inside an ancestor that clips its
     overflow, which plenty of themes do to kill sideways scrolling. Say so
     once in the console rather than leaving it a mystery. */
  (function reportClipping(){
    var el = column.parentNode, cs, culprit = null, name;
    while(el && el.nodeType === 1 && el !== document.documentElement){
      cs = window.getComputedStyle(el);
      if(/hidden|clip/.test(cs.overflow + " " + cs.overflowX + " " + cs.overflowY)){
        culprit = el; break;
      }
      el = el.parentNode;
    }
    if(culprit && window.console && console.warn){
      name = culprit.tagName.toLowerCase() +
             (culprit.id ? "#" + culprit.id : "") +
             (culprit.className ? "." + String(culprit.className).trim().split(/\s+/).join(".") : "");
      console.warn("Apple Calculator Language: the paper tape cannot park at the top of " +
        "the window, because " + name + " clips its overflow. To fix it, add this to your " +
        "theme CSS:\n\n    " + name + " { overflow: visible; }\n\nEverything else still works.");
    }
  })();

  layout();
  greet();
  refreshStatus();
})();
//-->
</script>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/the-apple-calculator-language/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">9214</post-id>	</item>
		<item>
		<title>Claude Opus 5 is limited to a 0.2M token context window by default</title>
		<link>https://wadetregaskis.com/claude-opus-5-is-limited-to-a-0-2m-token-context-window-by-default/</link>
					<comments>https://wadetregaskis.com/claude-opus-5-is-limited-to-a-0-2m-token-context-window-by-default/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 25 Jul 2026 21:55:15 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Anthropic]]></category>
		<category><![CDATA[Claude]]></category>
		<category><![CDATA[Claude Opus 4.8]]></category>
		<category><![CDATA[Claude Opus 5]]></category>
		<category><![CDATA[context window]]></category>
		<category><![CDATA[tokens]]></category>
		<category><![CDATA[Usage Credits]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=9194</guid>

					<description><![CDATA[Opus 4.8 and earlier use a 1M token context window (on any paid plan, at least). So it&#8217;s surprising that Anthropic quietly reduced this by 80% with Opus 5. You can get the 1M token context window back, though &#8211; you just have to enable Usage Credits. You don&#8217;t have to pay for any usage&#8230; <a class="read-more-link" href="https://wadetregaskis.com/claude-opus-5-is-limited-to-a-0-2m-token-context-window-by-default/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Opus 4.8 and earlier use a 1M token context window (on any paid plan, at least).  So it&#8217;s surprising that Anthropic quietly reduced this by 80% with Opus 5.</p>



<p class="wp-block-paragraph">You can get the 1M token context window back, though &#8211; you just have to enable Usage Credits.  You don&#8217;t have to <em>pay</em> for any usage credits, nor turn on auto-reload, nor have a non-zero limit.  You literally just have to have the Usage Credits toggle enabled. 🤷‍♂️</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img decoding="async" width="735" height="298" src="https://wadetregaskis.com/wp-content/uploads/2026/07/claude-usage-credits.webp" alt="Screenshot of the Claude Settings, showing the Usage credits section. The main toggle is enabled, but everything else is 'off' - $0 spending limit, no auto-reload." class="wp-image-9195" srcset="https://wadetregaskis.com/wp-content/uploads/2026/07/claude-usage-credits.webp 735w, https://wadetregaskis.com/wp-content/uploads/2026/07/claude-usage-credits-256x104.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/07/claude-usage-credits@2x.webp 1470w, https://wadetregaskis.com/wp-content/uploads/2026/07/claude-usage-credits-256x104@2x.webp 512w" sizes="(max-width: 735px) 100vw, 735px" /></figure>
</div>


<p class="wp-block-paragraph">Why does the context window size matter?  Especially for coding &#8211; where single tasks can be long-running and use very large numbers of tokens, for reading source files, mentally evaluating complex logic &amp; control flow, and so forth &#8211; it&#8217;s important that everything fit into a single context window, for maximum efficacy of the model.  If the model has to run compaction mid-way through a task, there&#8217;s a significant risk it&#8217;ll (a) &#8216;forget&#8217; important details, and therefore make more mistakes, and (b) have to re-read or re-compute things, wasting tokens (i.e. your precious usage quota).</p>



<p class="wp-block-paragraph">0.2M is pretty tiny by modern standards, and &#8211; because I do actually track token &amp; context window usage somewhat, for edification if nothing else &#8211; I know that I regularly use far more than that in everyday coding tasks.  In fact even 1M tokens isn&#8217;t a big enough window for the occasional task (though recent versions of Claude models tend to use subagents more aggressively, which helps avoid hitting the window limit &#8211; albeit at the expense of less token efficiency overall).</p>



<p class="wp-block-paragraph">As to <em>why</em> Anthropic did this… I suppose it&#8217;s conceivable that they believe Opus 5 is so good at carrying context across compaction that it can get away with it?  I haven&#8217;t tested it.  That&#8217;d be wonderful, if true &#8211; a smaller context window is less computationally costly (and much less costly if you&#8217;re conversing back-and-forth a lot within a session).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/claude-opus-5-is-limited-to-a-0-2m-token-context-window-by-default/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">9194</post-id>	</item>
		<item>
		<title>Apple&#8217;s notarytool can&#8217;t handle ZIP64 files</title>
		<link>https://wadetregaskis.com/apples-notarytool-cant-handle-zip64-files/</link>
					<comments>https://wadetregaskis.com/apples-notarytool-cant-handle-zip64-files/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 13 Jul 2026 17:21:35 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[notarytool]]></category>
		<category><![CDATA[Screen saver]]></category>
		<category><![CDATA[Zip]]></category>
		<category><![CDATA[Zip64]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=9164</guid>

					<description><![CDATA[In short: if you have any file in your app (or whatever it is you&#8217;re trying to get Apple to sign) that is over 4 GiB, you must use a disk image (DMG) to package it for notarytool, not a zip file. This is because support for files larger than 4 GiB requires using the&#8230; <a class="read-more-link" href="https://wadetregaskis.com/apples-notarytool-cant-handle-zip64-files/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">In short: if you have <em>any</em> file in your app (or whatever it is you&#8217;re trying to get Apple to sign) that is over 4 GiB, you must use a disk image (DMG) to package it for notarytool, not a zip file.</p>



<p class="wp-block-paragraph">This is because support for files larger than 4 GiB requires using the &#8220;ZIP64&#8221; zip file format, which would be fine except Apple&#8217;s notary servers can&#8217;t handle them. If you give them such a zip file, you&#8217;ll see notarytool fail something like this:</p>



<pre class="wp-block-preformatted"><em>$ xcrun notarytool submit "MyScreenSaver.saver.zip" --apple-id user-id --team-id team-id --verbose --wait --password lovesecretgod<br></em>[02:33:46.871Z] Debug [MAIN] Running notarytool version: 1.1.0 (39), date: 2026-07-13T02:33:46Z, command: /Applications/Xcode.app/Contents/Developer/usr/bin/notarytool submit MyScreenSaver.saver.zip --apple-id user-id --team-id team-id --verbose --wait --password private&lt;String&gt;<br>Conducting pre-submission checks for MyScreenSaver.saver.zip and initiating connection to the Apple notary service...<br>[02:33:46.943Z] Info [PREFLIGHT] Confirmed that MyScreenSaver.saver.zip (4.61 GB) is likely a PKZip file.<br>[02:33:46.943Z] Debug [PREFLIGHT] Finished completing determination of file type for MyScreenSaver.saver.zip. Operation took 71ms.<br>…<br>[05:09:56.690Z] Info [API] Preparing GET request to URL: https://appstoreconnect.apple.com/notary/v2/submissions/some-uuid?, Parameters: [:], Custom Headers: private&lt;Dictionary&lt;String, String&gt;&gt;<br>[05:09:56.690Z] Debug [AUTHENTICATION] Using cached token value for app-specific password request: team-id:user-id<br>[05:09:56.691Z] Debug [AUTHENTICATION] Authenticating request to '/notary/v2/submissions/some-uuid' with WebServices Token. AppleID: user-id, Team ID: team-id, Token: private&lt;String&gt;<br>[05:09:56.692Z] Debug [TASKMANAGER] Starting Task Manager loop to wait for asynchronous HTTP calls.<br>[05:09:57.115Z] Debug [API] Received response status code: 200, message: no error, URL: https://appstoreconnect.apple.com/notary/v2/submissions/some-uuid?, Correlation Key: some-key<br>[05:09:57.115Z] Debug [TASKMANAGER] Completed Task with ID 37 has received a parsable response.<br>[05:09:57.116Z] Debug [TASKMANAGER] Ending Task Manager loop.<br>[05:09:57.116Z] Info [API] <strong>Received new status: Invalid</strong><br>Current status: Invalid....................................<br>[05:09:57.116Z] Info [API] <strong>Submission in terminal status: Invalid</strong><br>Processing complete<br>  id: some-id<br>  status: Invalid<br>[05:09:57.138Z] Debug [CTXMGR] Removed temporary directory: Optional(file:///var/folders/v3/6w91zvg53dqgh_3fl858wtrh0000gn/T/TemporaryItems/NSIRD_notarytool_36y4rY/)</pre>



<p class="wp-block-paragraph">If you check the log, you&#8217;ll see absolutely nothing useful:</p>



<pre class="wp-block-preformatted"><em>$ xcrun notarytool log --apple-id user-id --team-id team-id --password lovesecretgod some-uuid<br></em>{<br>  "logFormatVersion": 1,<br>  "jobId": "some-uuid",<br>  "status": "Invalid",<br>  "statusSummary": "Archive contains critical validation errors",<br>  "statusCode": 4000,<br>  "archiveFilename": "MyScreenSaver.saver.zip",<br>  "uploadDate": "2026-07-13T05:02:27.652Z",<br>  "sha256": "cbafd582f7b3221af5f2930b9a97019da5275116610c46ad3874e7b392fd7196",<br>  "ticketContents": null,<br>  "issues": [<br>    {<br>      "severity": "error",<br>      "code": null,<br>      "path": "MyScreenSaver.saver.zip/MyScreenSaver.saver/Contents/MacOS/MyScreenSaver",<br>      "message": "The signature of the binary is invalid.",<br>      "docUrl": "https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/resolving_common_notarization_issues#3087735",<br>      "architecture": "x86_64"<br>    },<br>    {<br>      "severity": "error",<br>      "code": null,<br>      "path": "MyScreenSaver.saver.zip/MyScreenSaver.saver/Contents/MacOS/MyScreenSaver",<br>      "message": "The signature of the binary is invalid.",<br>      "docUrl": "https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/resolving_common_notarization_issues#3087735",<br>      "architecture": "arm64"<br>    }<br>  ]<br>}<br></pre>



<p class="wp-block-paragraph">The &#8216;help&#8217; URLs it links to are useless, since the problem is an outright bug in Apple&#8217;s infrastructure, not the input binary or bundle.  Indeed, Apple&#8217;s own tools confirm this:</p>



<pre class="wp-block-preformatted"><em>$ codesign -vvv --deep --strict /Users/me/Documents/MyScreenSaver.saver <br></em>/Users/me/Documents/MyScreenSaver.saver: valid on disk<br>/Users/me/Documents/MyScreenSaver.saver: satisfies its Designated Requirement</pre>



<p class="wp-block-paragraph">The only workaround I&#8217;ve been able to find is to package within a DMG instead.</p>



<p class="wp-block-paragraph">For the record, Apple, this is a stupid error on your part. 😡  While trying to figure out what the hell was actually going on, I spent multiple hours scouring the web, and experimenting, to no avail.  It took Claude Fable 5 a couple of hours too, but it <em>finally</em> cracked it.</p>



<p class="wp-block-paragraph">It&#8217;s especially irritating that I was only using zip files as a workaround for a design flaw in Apple&#8217;s notarytool, whereby it won&#8217;t accept the app bundle directly.  And <a href="https://developer.apple.com/documentation/security/customizing-the-notarization-workflow#:~:text=Because%20you%20can’t%20upload%20the%20.app%20bundle%20directly%20to%20the%20notary%20service%2C%20you’ll%20need%20to%20create%20a%20compressed%20archive%20containing%20the%20app" data-wpel-link="external" target="_blank" rel="external noopener">it&#8217;s Apple themselves that recommend using zip files specifically</a>.</p>



<p class="wp-block-paragraph">And lastly, it&#8217;s flabbergasting that this problem exists given that ZIP64 was first released &amp; publicly documented in 2001.  It&#8217;s 25 years later &#8211; why aren&#8217;t Apple supporting it?!  How did they even <em>find</em> a zip library that doesn&#8217;t support it?  Did they write their own specially just to remove support for it?  I cannot fathom how they&#8217;ve created this problem.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/apples-notarytool-cant-handle-zip64-files/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">9164</post-id>	</item>
		<item>
		<title>What&#8217;s Coming in Swift 6.4</title>
		<link>https://wadetregaskis.com/whats-coming-in-swift-6-4/</link>
					<comments>https://wadetregaskis.com/whats-coming-in-swift-6-4/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 08 Jun 2026 19:33:29 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=9117</guid>

					<description><![CDATA[This is a summary of the Swift Evolution Proposals that are marked as Implemented in 6.4. Note that I have not personally verified their implementation. Async inside defer SE-493 The enclosing context &#8211; the function &#8220;f&#8221; in the above example &#8211; must be async, of course. Type inference will work in closures, too; they&#8217;ll implicitly&#8230; <a class="read-more-link" href="https://wadetregaskis.com/whats-coming-in-swift-6-4/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is a summary of <a href="https://www.swift.org/swift-evolution/#?version=6.4" data-wpel-link="external" target="_blank" rel="external noopener">the Swift Evolution Proposals that are marked as Implemented in 6.4</a>.  Note that I have not personally verified their implementation.</p>



<h2 class="wp-block-heading">Async inside defer</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0493-defer-async.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-493</a></p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">f</span><span style="color: #000000">() </span><span style="color: #AF00DB">async</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">setUp</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">defer</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">performAsyncTeardown</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">  </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #795E26">doSomething</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">The enclosing context &#8211; the function &#8220;f&#8221; in the above example &#8211; must be <code>async</code>, of course.  Type inference will work in closures, too; they&#8217;ll implicitly be <code>async</code> if they contain any <code>await</code>s in defers.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #008000">// &#39;f&#39; implicitly has type &#39;() async -&gt; ()&#39;</span></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> f = {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">defer</span><span style="color: #000000"> { </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">g</span><span style="color: #000000">() }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Cautions:</p>



<ul class="wp-block-list">
<li>Use of async in defer adds additional suspension points to the end of the function.  Though this is not much different from the existing hazard that defer introduces, in placing code obliquely at the end of the function.</li>



<li>Task cancellation works like normal inside defer, so care needs to be taken that clean-up actually happens and isn&#8217;t short-circuited.  Again, this is just the same as it&#8217;s always been for non-async code inside defer (though async code is more likely to check for and react to cancellation).  But, it can be guarded against using…</li>
</ul>



<h2 class="wp-block-heading">Task cancellation shields</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0504-task-cancellation-shields.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-504</a></p>



<p class="wp-block-paragraph">This allows you to hide task cancellation status within a block (and stop automatic propagation of cancellation to subtasks). This is particularly helpful for clean-up code that wants to ensure it actually runs, even if it calls general helper functions that normally do observe cancellation.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">doWork</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">while</span><span style="color: #000000"> !Task.</span><span style="color: #001080">isCancelled</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> result = </span><span style="color: #795E26">crunchNumbers</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">send</span><span style="color: #000000">(.</span><span style="color: #001080">latestResult</span><span style="color: #000000">, result)</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">  </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// Clean up</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">send</span><span style="color: #000000">(.</span><span style="color: #001080">goodbye</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">send</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">type</span><span style="color: #000000">: MessageType, </span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">payload</span><span style="color: #000000">: Data? = </span><span style="color: #0000FF">nil</span><span style="color: #000000">) { </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> !Task.</span><span style="color: #001080">isCancelled</span><span style="color: #000000"> </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  …</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">The &#8216;goodbye&#8217; message in that example would never actually be sent, and worse that failure is completely silent (because you typically don&#8217;t want to log &#8220;Cancelled!&#8221; everywhere, as it&#8217;s very noisy in what may be a relatively common case &#8211; depending on how you &amp; your dependencies use task cancellation).</p>



<p class="wp-block-paragraph">With Swift 6.4 you can write:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">doWork</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">while</span><span style="color: #000000"> !Task.</span><span style="color: #001080">isCancelled</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> result = </span><span style="color: #795E26">crunchNumbers</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">send</span><span style="color: #000000">(.</span><span style="color: #001080">latestResult</span><span style="color: #000000">, result)</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">  </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// Clean up</span></span>
<span class="line"><span style="color: #000000">  withTaskCancellationShield { </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">send</span><span style="color: #000000">(.</span><span style="color: #001080">goodbye</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">send</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">type</span><span style="color: #000000">: MessageType, </span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">payload</span><span style="color: #000000">: Data? = </span><span style="color: #0000FF">nil</span><span style="color: #000000">) { </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> !Task.</span><span style="color: #001080">isCancelled</span><span style="color: #000000"> </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  …</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>send</code> will read <em>false</em> from Task.isCancelled (as would any children, function- or structured-task-wise) and thus actually send the goodbye now.</p>



<p class="wp-block-paragraph">Caution:</p>



<ul class="wp-block-list">
<li>Hiding cancellation status from arbitrary code might lead to unbounded delays in cancellation completing, because the code under the shield sees no reason to finish promptly.  Cancellation usually has <em>some</em> degree of time-sensitivity, e.g. if a user cancelled the task &#8211; or asked the program to quit, cancelling any work in progress &#8211; then they might tolerate a few seconds of delay, but won&#8217;t be pleased if the program just &#8220;hangs&#8221; (takes so long they start questioning whether it&#8217;s actually cancelling at all).</li>



<li>Since cancellation propagation to subtasks is blocked while the shield is in place, you may need to do extra work to ensure subtasks are appropriately cancelled (e.g. if you exit the cancellation shield &#8216;early&#8217;, you probably <em>do</em> want the subtasks to then cancel too &#8211; but you&#8217;ll have to explicitly cancel them yourself).</li>



<li>Conversely, task cancellation shields do <em>not</em> block explicit cancellation of subtasks, so you must be careful wherever you have cancellation handlers or code that might show a little too much initiative in cancelling tasks.</li>
</ul>



<h2 class="wp-block-heading">Async Result Support</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0530-async-result-support.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-530</a></p>



<p class="wp-block-paragraph">Now there&#8217;s a way to initialise a <code>Result</code> from an async closure, through a new initialiser.  It works exactly like you&#8217;d expect:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> result = </span><span style="color: #AF00DB">await</span><span style="color: #000000"> Result {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #AF00DB">await</span><span style="color: #000000"> </span><span style="color: #795E26">asyncWork</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h2 class="wp-block-heading">More standard library types support ~Escapable and ~Copyable</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0499-support-non-copyable-simple-protocols.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-499</a></p>



<p class="wp-block-paragraph">As of Swift 6.4, these protocols will <em>no longer</em> require the conforming types to be <code>Copyable</code> and <code>Escapable</code>:</p>



<ul class="wp-block-list">
<li><code>Equatable</code></li>



<li><code>Comparable</code></li>



<li><code>Hashable</code></li>



<li><code>CustomStringConvertible</code></li>



<li><code>CustomDebugStringConvertible</code></li>



<li><code>TextOutputStream</code></li>



<li><code>TextOutputStreamable</code></li>
</ul>



<p class="wp-block-paragraph">Additionally, <code>LosslessStringConvertible</code>&nbsp;no longer requires <code>Copyable</code>, and both&nbsp;<code>Optional</code>&nbsp;and&nbsp;<code>Result</code>&nbsp;now support&nbsp;<code>~Copyable</code>&nbsp;and&nbsp;<code>~Escapable</code>&nbsp;elements.  The latter is an especially big improvement for users of <code>~Copyable</code> or <code>~Escapable</code> types, given how fundamental (and nice!) <code>Optional</code> is to idiomatic Swift.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Handle</span><span style="color: #000000">: ~Copyable {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">var</span><span style="color: #000000"> uuid: InlineArray&lt;16, </span><span style="color: #267F99">UInt8</span><span style="color: #000000">&gt;</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Now valid in Swift 6.4, so you can e.g. use Handle as the key in a collection!</span></span>
<span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">Handle</span><span style="color: #000000">: Hashable {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">static</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">==</span><span style="color: #000000"> (</span><span style="color: #795E26">lhs</span><span style="color: #000000">: borrowing Handle, </span><span style="color: #795E26">rhs</span><span style="color: #000000">: borrowing Handle) -&gt; </span><span style="color: #267F99">Bool</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    lhs.</span><span style="color: #001080">uuid</span><span style="color: #000000"> == rhs.</span><span style="color: #001080">uuid</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">hash</span><span style="color: #000000">(</span><span style="color: #795E26">into</span><span style="color: #000000"> </span><span style="color: #001080">hasher</span><span style="color: #000000">: </span><span style="color: #0000FF">inout</span><span style="color: #000000"> Hasher) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">for</span><span style="color: #000000"> b </span><span style="color: #AF00DB">in</span><span style="color: #000000"> bytes {</span></span>
<span class="line"><span style="color: #000000">      hasher.</span><span style="color: #795E26">combine</span><span style="color: #000000">(b)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// Now valid in Swift 6.4, so you can e.g. use Handle in log messages!</span></span>
<span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">Handle</span><span style="color: #000000">: CustomDebugStringConvertible {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> debugDescription: </span><span style="color: #267F99">String</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #A31515">&quot;Handle&lt;</span><span style="color: #0000FF">\(</span><span style="color: #000000FF">uuid.</span><span style="color: #001080">description</span><span style="color: #0000FF">)</span><span style="color: #A31515">&gt;&quot;</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// As of Swift 6.4, `createHandle` can return a `Handle?` like you&#39;d expect!</span></span>
<span class="line"><span style="color: #AF00DB">guard</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> h = </span><span style="color: #795E26">createHandle</span><span style="color: #000000">() </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">return</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">handleCreationTimes&#91;h&#93; = .</span><span style="color: #001080">now</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Created new handle: </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">h</span><span style="color: #0000FF">)</span><span style="color: #A31515">&quot;</span><span style="color: #000000">)</span></span></code></pre></div>



<p class="wp-block-paragraph">These are essentially bug fixes &#8211; these protocols never semantically required copyability or escapability, but because the notions of being non-copyable or non-escapable came longer after they were first defined, and the way Swift makes copyability and escapability the <em>default</em>, they needed  their definitions to actually be changed in order to accomodate.  It&#8217;s unfortunate that this wasn&#8217;t done when <code>~Copyable</code> and <code>~Escapable</code> were introduced in Swift 5.9 and 6.2 (respectively).  Alas it&#8217;s common practice for the standard library to <em>intentionally</em> be left out of sync with the language (in some cases it&#8217;s because other dependencies, like borrowing, had to be developed in order to facilitate the fixes &#8211; the proposal notes that <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0499-support-non-copyable-simple-protocols.md#future-directions" data-wpel-link="external" target="_blank" rel="external noopener">many more standard library types still need similar fixes, but lack the necessary compiler support</a>).</p>



<h2 class="wp-block-heading">Private member variables with default values are excluded (by default) from compiler-generated initialisers</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0502-exclude-private-from-memberwise-init.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-502</a></p>



<p class="wp-block-paragraph">A mouthful to describe, but actually a simple change &#8211; the following code will now work, as of Swift 6.4, instead of failing with the error &#8220;initializer is inaccessible due to &#8216;private&#8217; protection level&#8221;:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">S</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> x: </span><span style="color: #267F99">Int</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> y: </span><span style="color: #267F99">Int</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> z = </span><span style="color: #098658">0</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> s = </span><span style="color: #795E26">S</span><span style="color: #000000">(</span><span style="color: #795E26">x</span><span style="color: #000000">: </span><span style="color: #098658">1</span><span style="color: #000000">, </span><span style="color: #795E26">y</span><span style="color: #000000">: </span><span style="color: #098658">2</span><span style="color: #000000">)</span></span></code></pre></div>



<p class="wp-block-paragraph">Previously, the initialiser that the compiler implicitly generated looked like:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">x</span><span style="color: #000000">: </span><span style="color: #267F99">Int</span><span style="color: #000000">, </span><span style="color: #795E26">y</span><span style="color: #000000">: </span><span style="color: #267F99">Int</span><span style="color: #000000">, </span><span style="color: #795E26">z</span><span style="color: #000000">: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">0</span><span style="color: #000000">)</span></span></code></pre></div>



<p class="wp-block-paragraph">Now, it looks like:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">x</span><span style="color: #000000">: </span><span style="color: #267F99">Int</span><span style="color: #000000">, </span><span style="color: #795E26">y</span><span style="color: #000000">: </span><span style="color: #267F99">Int</span><span style="color: #000000">)</span></span></code></pre></div>



<p class="wp-block-paragraph">This makes it easier to use implicit initialisers by increasing the number of situations in which you can utilise them, and also makes it easier and safer to add additional private member variables later.</p>



<p class="wp-block-paragraph">Caveats:</p>



<ul class="wp-block-list">
<li>If you were actually relying on the private member variable(s) being part of the initialiser &#8211; and didn&#8217;t mind the implicit access restriction &#8211; then this will ultimately be a regression.  However, for now Swift continues to <em>also</em> provide the old initialiser, for backwards compatibility (but <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0502-exclude-private-from-memberwise-init.md#deprecating-the-compatibility-memberwise-initializer" data-wpel-link="external" target="_blank" rel="external noopener">the intent is to remove it in a future Swift release</a>).  Also, it&#8217;s not foolproof &#8211; <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0502-exclude-private-from-memberwise-init.md#source-compatibility" data-wpel-link="external" target="_blank" rel="external noopener">some uncommon cases will still see a source-incompatible change</a>.</li>



<li>This does not extend to <code>open</code> / <code>public</code> / <code>package</code> structs &#8211; if you have a struct accessible at one of those levels, its implicit initialiser will continue to behave as in prior versions of Swift, i.e. it will still include all the member variables and thus be limited to the accessibility of the most restricted variable.  This is intentional, though &#8211; it&#8217;s to prevent module API changes.</li>
</ul>



<h2 class="wp-block-heading">Borrow and Mutate Accessors</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0507-borrow-accessors.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-507</a></p>



<p class="wp-block-paragraph">The final piece of the <a href="https://forums.swift.org/t/prospective-vision-accessors/76707" data-wpel-link="external" target="_blank" rel="external noopener">Prospective Vision for Accessors</a>, this allows for more efficient accessors on <code>struct</code>s, in a nutshell.</p>



<p class="wp-block-paragraph">In older versions of Swift, a getter had to return [semantically] a <em>copy</em> of the value of interest.  If that value was expensive to copy (e.g. a large struct) then that made the accessor slow.  And it entirely precluded getters for <code>~Copyable</code> types.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">RigidWrapper</span><span style="color: #000000">&lt;</span><span style="color: #0000FF">Element</span><span style="color: #000000">: </span><span style="color: #267F99">~Copyable</span><span style="color: #000000">&gt;: ~Copyable {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> _element: </span><span style="color: #267F99">Element</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> element: </span><span style="color: #267F99">Element</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    borrow {</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #AF00DB">return</span><span style="color: #000000"> _element</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    mutate {</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #AF00DB">return</span><span style="color: #000000"> &amp;_element</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Where inlining is permitted, the above code basically boils down to direct [indexed] pointer access to <code>_element</code> by all callers, without any safety concerns (the compiler continues to enforce correct lifetimes, access permissions, and mutual exclusion). That&#8217;s basically as efficient as it gets.</p>



<p class="wp-block-paragraph">Like many of the Swift 6.4 features, this is of direct interest to only a few Swift users (e.g. authors of packages like <a href="https://github.com/apple/swift-collections" data-wpel-link="external" target="_blank" rel="external noopener">swift-collections</a>) or in uncommon cases (e.g. optimising the occasional hot-spot).  But it will be beneficial to all Swift users by virtue of quietly faster and more efficient types in the standard library, Foundation, and other packages.</p>



<p class="wp-block-paragraph">Caveats:</p>



<ul class="wp-block-list">
<li>These new accessor types are only supported on <code>struct</code>s, not classes or actors, for now.</li>



<li>You can only return a <em>single</em> variable, which must be deterministic at compile-time. So no <code>if xyz { return _a } else { return _b }</code>.</li>



<li>The returned values must outlive the accessor call, so these accessors can&#8217;t return a local variable or a computed value or similar (unless it too is a borrowed / mutating return with appropriate lifetime). They are really only intended to provide direct, optimally-efficient access to stored properties (though <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0507-borrow-accessors.md#ownership-variations" data-wpel-link="external" target="_blank" rel="external noopener">more advanced usage does permit returning stored values from <em>outside</em> the struct</a>).</li>



<li>You can&#8217;t mutate an object while you have borrowed or mutating references from it, through these accessors.  This is part of the correctness enforcement.  It&#8217;s a little heavy-handed &#8211; depending on the object&#8217;s semantics, it might be perfectly safe to mutate <em>other</em> stored properties within it, but for now the Swift compiler doesn&#8217;t have a way to deduce that nor does the Swift language have a way to specify that.  So you may need to be careful about lifetimes and order of operations.  Also…</li>



<li>You can&#8217;t have a &#8216;regular&#8217; <code>get</code> accessor and a <code>borrow</code> accessor; they are mutually exclusive.  This does unfortunately mean the upgrade path is complicated, as <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0507-borrow-accessors.md#implications-on-adoption" data-wpel-link="external" target="_blank" rel="external noopener">adopting <code>borrow</code> accessors can be source-incompatible and binary-incompatible</a>.</li>
</ul>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">🗒 Swift 6.2 <em>sort of</em> added another approach to this, <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0474-yielding-accessors.md" data-wpel-link="external" target="_blank" rel="external noopener">yielding accessors</a>, though even in Swift 6.4 those remain guarded behind a compiler feature flag. Additionally, they typically don&#8217;t perform as a well, nor are as simple to write (but have other uses, for cases where the accessor needs to do something after the caller has finished with the value, such as updating an index).</p>
</div></div>



<h2 class="wp-block-heading"><code>withTemporaryAllocation</code> using <code>OutputSpan</code> / <code>OutputRawSpan</code></h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0524-span-temporary-allocation.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-524</a></p>



<p class="wp-block-paragraph">Now there&#8217;s a safer version of the existing <code><a href="https://developer.apple.com/documentation/swift/withunsafetemporaryallocation(of:capacity:_:)" data-wpel-link="external" target="_blank" rel="external noopener">withUnsafeTemporaryAllocation</a></code> (<a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0322-temporary-buffers.md" data-wpel-link="external" target="_blank" rel="external noopener">first introduced in Swift 5.6</a>). It works just like you&#8217;d expect.  It comes in both &#8220;raw bytes&#8221; and typed elements versions, just like its unsafe predecessor.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> result = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #795E26">withTemporaryAllocation</span><span style="color: #000000">(</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #795E26">of</span><span style="color: #000000">: MyResult.self,</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #795E26">capacity</span><span style="color: #000000">: </span><span style="color: #098658">42</span></span>
<span class="line"><span style="color: #000000">) { output -&gt; </span><span style="color: #267F99">Int</span><span style="color: #000000"> </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">for</span><span style="color: #000000"> i </span><span style="color: #AF00DB">in</span><span style="color: #000000"> </span><span style="color: #098658">0</span><span style="color: #000000">..&lt;capacity {</span></span>
<span class="line"><span style="color: #000000">    output.</span><span style="color: #795E26">append</span><span style="color: #000000">(</span><span style="color: #795E26">calculateResult</span><span style="color: #000000">())</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #795E26">processResults</span><span style="color: #000000">(output.</span><span style="color: #001080">span</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">What makes this &#8216;safe&#8217; is that, unlike its predecessor:</p>



<ol class="wp-block-list">
<li>It guarantees to correctly deinitialise the contents of the allocation (i.e. all the elements you appended). i.e. it&#8217;ll run <code>deinit</code> or any other such teardown required to properly clean up elements when they&#8217;re removed (or when the temporary allocation goes away).</li>



<li>Per the normal <code>OutputSpan</code> contract, it won&#8217;t let you write to invalid indices nor read uninitialised elements (by default &#8211; there is <a href="https://developer.apple.com/documentation/swift/outputspan/subscript(unchecked:)" data-wpel-link="external" target="_blank" rel="external noopener">an unsafe version of the subscript operator</a> that bypasses this check, if you want to live fast and dangerous).</li>
</ol>



<p class="wp-block-paragraph">The API is also much nicer, as <code>OutputSpan</code> / <code>OutputRawSpan</code> are much more like regular collections than the byzantine API of <code>UnsafeMutableBufferPointer</code> / <code>UnsafeRawMutableBufferPointer</code> (used by <code>withUnsafeTemporaryAllocation</code>).</p>



<h2 class="wp-block-heading">~Sendable</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0518-tilde-sendable.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-518</a></p>



<p class="wp-block-paragraph">Analogous to <code>~Copyable</code> and <code>~Escapable</code>, it&#8217;s now possible to explicitly opt out of <code>Sendable</code> conformance.</p>



<p class="wp-block-paragraph">The primary motivation is to provide clarity &#8211; the lack of an explicit <code>Sendable</code> conformance no longer has to be ambiguous as to whether sendability is or isn&#8217;t intended.  It can also prevent unintended sendability changes &#8211; important for stable APIs.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #008000">// Either not sendable right now, or expected to be non-sendable in future.</span></span>
<span class="line"><span style="color: #0000FF">public</span><span style="color: #000000"> </span><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">NotThreadSafe</span><span style="color: #000000">: ~Sendable {</span></span>
<span class="line"><span style="color: #000000">  …</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">It also enables selective sendability in a class hierarchy &#8211; e.g. a <code>~Sendable</code> base class with <code>Sendable</code> subclasses. Previously it was possible to express the semantic of non-sendable, but in a somewhat hacky way (using <code>@unavailable</code>) and applying irreversibly to all descendants.  Now, it&#8217;s straightforward and simple:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">Base</span><span style="color: #000000">: ~Sendable {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// Some internals which are not sendable, or the contract is to not presume sendability for subclasses.</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">Sub</span><span style="color: #000000">: Base, @unchecked Sendable {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// Overrides whatever it was that makes `Base` non-sendable, or is just a subclass which choses to be Sendable even though it&#39;s not required by the ancestor(s).</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">You can also add <em>conditional</em> Sendability through conditional type extensions.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #008000">// Could hold anything, including non-sendable types, so not naturally sendable.</span></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">MyContainer</span><span style="color: #000000">&lt;</span><span style="color: #0000FF">T</span><span style="color: #000000">&gt;: ~Sendable {  </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> contents: T</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// But automatically Sendable if the contents are.</span></span>
<span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">MyContainer</span><span style="color: #000000">: Sendable </span><span style="color: #0000FF">where</span><span style="color: #000000"> T: Sendable {}</span></span></code></pre></div>



<p class="wp-block-paragraph">Caveats:</p>



<ul class="wp-block-list">
<li>Explicit <code>~Sendable</code> on a type declaration, that&#8217;s then overridden by an extension, sends mixed signals and may confuse readers as to the author&#8217;s intent.</li>



<li><code>~Sendable</code> has to be declared on the type declaration itself, not in an extension.  Similar to other ~Foo declarations.</li>



<li>Unlike e.g. <code>~Escapable</code>, sendability is not presumed on types, so there&#8217;s no need (and it&#8217;s not supported) to declare <code>~Sendable</code> in generics. Not-sendable is already assumed. The <code>~Sendable</code> declaration is more for <em>humans</em> than the compiler, to more clearly express <em>intent</em> (and correspondingly it has no impact on the ABI).</li>
</ul>



<h2 class="wp-block-heading">Compiler warning for silently swallowing errors inside a Task</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0520-discardableresult-task-initializers.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-520</a></p>



<p class="wp-block-paragraph">Previously, code like this would compile without any complaints, even though it is likely faulty:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">Task {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #795E26">boom</span><span style="color: #000000">() </span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">print</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;Yay!&quot;</span><span style="color: #000000">)</span></span></code></pre></div>



<p class="wp-block-paragraph">Now, that will issue a compiler warning:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<pre class="wp-block-preformatted">warning: Unstructured throwing task was not used, which may accidentally ignore errors thrown inside the task [#NoUseUnstructuredThrowingTask]</pre>
</blockquote>



<p class="wp-block-paragraph">The fix is, of course, to do something about the potential exception &#8211; either catch &amp; handle it inside the <code>Task</code>, or actually do something with the returned <code>Task</code> instance.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">🗒 As an interesting implementation detail, this is not done merely by removing <code>@discardableResult</code> from the declaration. That was considered but rejected because of the API pollution it creates and because it&#8217;s semantically a bit different. Instead, this is truly a bespoke compiler warning, focused <em>solely</em> on whether the error is handled, not whether the [non-error] result is used.</p>
</div></div>



<h2 class="wp-block-heading">isTriviallyIdentical</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0494-add-is-identical-methods.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-494</a></p>



<p class="wp-block-paragraph">The new <code>isTriviallyIdentical(to:)</code> method offers a guaranteed-fast way to check two objects for equality. It is essentially an optimised version of the existing equality condition provided by <code>Equatable</code> and the <code>==</code> operator. It&#8217;s useful because it <em>guarantees</em> O(1) time complexity, whereas <code>==</code> makes no formal guarantees at all (and for many types is O(N) or worse). But unlike the existing <em>identity</em> comparison operator <code>===</code> &#8211; also O(1) &#8211; it promises to do more than just tell if two objects are the exact same pointer, <em>and</em> it works on value types (not just reference types).</p>



<p class="wp-block-paragraph">The catch is that it&#8217;s not available for all types.  It&#8217;s opt-in, initially for a fairly broad but finite set of collection types in the standard library (see <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0494-add-is-identical-methods.md#detailed-design" data-wpel-link="external" target="_blank" rel="external noopener">the complete list in the proposal</a>).</p>



<p class="wp-block-paragraph">The way it typically works, in the initial set of implementations at least, is to check if the <em>underlying</em> shared storage is the same, between two objects that either utilise a copy-on-write mechanic (most standard library collections, like <code>Array</code>, <code>Set</code>, etc) or are wrappers on raw memory (<code>Unsafe*BufferPointer</code>s and <code>*Span</code>).</p>



<p class="wp-block-paragraph">Note that there&#8217;s nothing in its definition that restricts it to just copy-on-write or raw-pointery types (e.g. most if not all <code>Identifiable</code> types can implement <code>isTriviallyIdentical(to:)</code>, as just comparing IDs<sup data-fn="3a041e14-bcbd-482c-8579-e044ad71c434" class="fn"><a href="#3a041e14-bcbd-482c-8579-e044ad71c434" id="3a041e14-bcbd-482c-8579-e044ad71c434-link">1</a></sup>).</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">update</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">contacts</span><span style="color: #000000">: &#91;Contact&#93;) {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">guard</span><span style="color: #000000"> !</span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">cachedContacts</span><span style="color: #000000">.</span><span style="color: #795E26">isTriviallyIdentical</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000">: contacts) </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// `contacts` is _guaranteed_ identical (==) to our cached copy.</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">  </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// `contacts` _might_ or might not be identical to our cached copy.</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// We could try `==` for a more precise check, but depending on what we&#39;re doing, it might be more efficient to just do our work - e.g. a scan of the contacts checking for some criteria is O(N) and `==` will be O(N) and possibly even slower (since it may have to check _every_ contact field for equality, not merely the subset of fields we&#39;re actually interested in).</span></span>
<span class="line"><span style="color: #000000">  </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">cachedContacts</span><span style="color: #000000"> = contacts</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// &lt;expensive operation here, like scanning all the contacts&gt;</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Note that there&#8217;s nothing preventing the same check being part of <code>==</code> for the types that implement this (and many likely already contain this optimisation).  But the big difference is that <code>Equatable</code>&#8216;s <code>==</code> usually <em>has</em> to fall back to an exhaustive recursive check, using <code>==</code>, whereas <code>isTriviallyIdentical(to:)</code> by contract does not.</p>



<p class="wp-block-paragraph">Caveats &amp; cautions:</p>



<ul class="wp-block-list">
<li>There&#8217;s no new protocol defined, so it&#8217;s very difficult to make use of this new feature with generic code.  This was <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0494-add-is-identical-methods.md#generic-contexts" data-wpel-link="external" target="_blank" rel="external noopener">discussed in the proposal</a> but no satisfying reason given for the omission &#8211; the authors seem to believe that supporting generics isn&#8217;t important.</li>



<li>Similarly, <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0494-add-is-identical-methods.md#support-for-optionals" data-wpel-link="external" target="_blank" rel="external noopener">the authors explicitly chose not to support this transitively on common types like <code>Optional</code></a>, intentionally punting that work to every Swift programmer.</li>



<li>A fallback to <code>==</code> is not a good idea in some cases, so be wary of making that a habit or pattern.  Generally if you <em>actually</em> need to know equality precisely, you should just use <code>==</code>.</li>
</ul>



<h2 class="wp-block-heading"><code>Hashable</code> conformance for <code>UnownedTaskExecutor</code>, <code>Dictionary.Keys</code>, <code>CollectionOfOne</code>, and <code>EmptyCollection</code></h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0514-hashable-conformance-for-dictionarykeys-collectionofone-emptycollection.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-514</a> &amp; <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0523-hashable-unownedtask-executor.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-523</a></p>



<p class="wp-block-paragraph">This simply fixes some oversights in the standard library.</p>



<p class="wp-block-paragraph">Note that for <code>CollectionOfOne</code> and <code>EmptyCollection</code> conformance is conditional on the <code>Element</code> type being <code>Hashable</code>. It&#8217;s unilateral for <code>Dictionary.Keys</code> since <code>Dictionary</code> always requires <code>Key</code> to be <code>Hashable</code>, and unilateral for <code>UnownedTaskExecutor</code> by nature.</p>



<p class="wp-block-paragraph">One obvious and notable benefit of <code>UnownedTaskExecutor</code>&#8216;s conformance is that, because it&#8217;s relatively common to store collections of such executors, such as a connection pool or cache, that pool or cache can be <em>much</em> faster. Previously the only way to check if an executor was already in such a pool was to do a linear search (yuck!):</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">pickConnection</span><span style="color: #000000">(</span><span style="color: #795E26">preferring</span><span style="color: #000000"> </span><span style="color: #001080">executor</span><span style="color: #000000">: UnownedTaskExecutor) -&gt; Connection {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">for</span><span style="color: #000000"> (e, connection) </span><span style="color: #AF00DB">in</span><span style="color: #000000"> executorConnectionList {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> e == executor { </span><span style="color: #AF00DB">return</span><span style="color: #000000"> connection }</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">return</span><span style="color: #000000"> executorConnectionList.</span><span style="color: #001080">first</span><span style="color: #000000">!.</span><span style="color: #001080">connection</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">…whereas now it can be a much faster constant-time lookup:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">pickConnection</span><span style="color: #000000">(</span><span style="color: #795E26">preferring</span><span style="color: #000000"> </span><span style="color: #001080">executor</span><span style="color: #000000">: UnownedTaskExecutor) -&gt; Connection {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> connection = connectionsByExecutor&#91;executor&#93; {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> connection</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">return</span><span style="color: #000000"> connectionsByExecutor.</span><span style="color: #001080">values</span><span style="color: #000000">.</span><span style="color: #001080">first</span><span style="color: #000000">!</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h2 class="wp-block-heading">Ref and MutableRef types</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0519-ref-mutableref-types.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-519</a></p>



<p class="wp-block-paragraph">These new types generalise the existing patterns of borrowed and mutable references, that have previously existed in Swift in much more restricted forms (e.g. <code>inout</code> parameters are essentially <code>MutableRef</code> parameters, and <code>Span</code> &amp; <code>MutableSpan</code> are array equivalents).</p>



<p class="wp-block-paragraph">This allows for more control and therefore more precise use of borrows / references.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #008000">// Fetch the value *once*…</span></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> entry = </span><span style="color: #795E26">MutableRef</span><span style="color: #000000">(&amp;dictionary&#91;key, </span><span style="color: #795E26">default</span><span style="color: #000000">: </span><span style="color: #098658">0</span><span style="color: #000000">&#93;)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #008000">// ...and repeatedly modify it, without repeatedly looking it up again in the Dictionary&#39;s hash table.  Much more efficient!</span></span>
<span class="line"><span style="color: #AF00DB">for</span><span style="color: #000000"> value </span><span style="color: #AF00DB">in</span><span style="color: #000000"> values {</span></span>
<span class="line"><span style="color: #000000">  entry.</span><span style="color: #001080">value</span><span style="color: #000000"> += value</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">It can be combined with still-experimental lifetime annotations to enable some pretty powerful optimisations, e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">struct</span><span style="color: #000000"> </span><span style="color: #267F99">Vec3</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">var</span><span style="color: #000000"> x, y, z: </span><span style="color: #267F99">Double</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">@_lifetime</span><span style="color: #000000">(&amp;</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">mutating</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">at</span><span style="color: #000000">(</span><span style="color: #795E26">index</span><span style="color: #000000">: </span><span style="color: #267F99">Int</span><span style="color: #000000">) -&gt; MutableRef&lt;</span><span style="color: #267F99">Double</span><span style="color: #000000">&gt; {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">switch</span><span style="color: #000000"> index {</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #AF00DB">case</span><span style="color: #000000"> </span><span style="color: #098658">0</span><span style="color: #000000">: </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #795E26">MutableRef</span><span style="color: #000000">(&amp;x)</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #AF00DB">case</span><span style="color: #000000"> </span><span style="color: #098658">1</span><span style="color: #000000">: </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #795E26">MutableRef</span><span style="color: #000000">(&amp;y)</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #AF00DB">case</span><span style="color: #000000"> </span><span style="color: #098658">2</span><span style="color: #000000">: </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #795E26">MutableRef</span><span style="color: #000000">(&amp;z)</span></span>
<span class="line"><span style="color: #000000">      </span><span style="color: #AF00DB">default</span><span style="color: #000000">:</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">fatalError</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;out of bounds&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Previously &#8211; and even with the new borrow &amp; mutate accessors &#8211; this would require at least two accesses to the struct, one to fetch the value and then another to set it later. More importantly, it would not guarantee exclusive access to the relevant field in the meantime, so other code could modify the struct in-between the get and the set. The <code>MutableRef</code> <em>guarantees</em> exclusive access to the struct while it exists.</p>



<p class="wp-block-paragraph">Caveats:</p>



<ul class="wp-block-list">
<li>Only <code>Escapable</code> types can be used with <code>Ref</code> and <code>MutableRef</code>, for now.  <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0519-ref-mutableref-types.md#escapable-target-types" data-wpel-link="external" target="_blank" rel="external noopener">This is due to compiler limitations that will hopefully be addressed in future</a>.</li>
</ul>



<h2 class="wp-block-heading">Array expression trailing closures</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0508-array-expression-trailing-closures.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-508</a></p>



<p class="wp-block-paragraph">The compiler now supports trailing closure syntax for <code>Array</code> and <code>Dictionary</code>. Previously it got a bit confused because it tried to interpret the trailing closure as an array or dictionary literal, or as a random unrelated and uncalled closure, requiring more verbose initialisation syntax as a workaround. Now, intuitive code like this just works:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">Array</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">init</span><span style="color: #000000">(@</span><span style="color: #795E26">ArrayBuilder</span><span style="color: #000000"> </span><span style="color: #001080">build</span><span style="color: #000000">: () -&gt; &#91;</span><span style="color: #267F99">Element</span><span style="color: #000000">&#93;) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000"> = </span><span style="color: #795E26">build</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> value = &#91;</span><span style="color: #267F99">String</span><span style="color: #000000">&#93; {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #A31515">&quot;a&quot;</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #A31515">&quot;b&quot;</span></span>
<span class="line"><span style="color: #000000">  </span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">if</span><span style="color: #000000"> bonusItem {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #A31515">&quot;c&quot;</span></span>
<span class="line"><span style="color: #000000">  }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">This is interesting mainly in the case where you use a result builder, such as <a href="https://github.com/WilhelmOks/ArrayBuilder" data-wpel-link="external" target="_blank" rel="external noopener">ArrayBuilder</a> as shown above.  Note that <code>ArrayBuilder</code> is not currently part of Swift proper, or any Apple-owned packages, but <a href="https://forums.swift.org/t/add-arraybuilder-to-the-standard-library/83811" data-wpel-link="external" target="_blank" rel="external noopener">its inclusion has been proposed</a>.</p>



<h2 class="wp-block-heading">The <code>demangle</code> function</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0498-runtime-demangle.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-498</a></p>



<p class="wp-block-paragraph">There&#8217;s now a built-in function for demangling Swift symbol names, in the <code>Runtime</code> module (that&#8217;s bundled with the Swift toolchain, alongside the stdlib etc). This won&#8217;t be of <em>direct</em> interest to most Swift users, but for certain package developers (such as for crash reporting) this may be very helpful.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> demangledOutputSpan: OutputSpan&lt;</span><span style="color: #267F99">UTF8</span><span style="color: #000000">.</span><span style="color: #267F99">CodeUnit</span><span style="color: #000000">&gt; = ...</span></span>
<span class="line"></span>
<span class="line"><span style="color: #AF00DB">do</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #795E26">demangle</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;$sSS7cStringSSSPys4Int8VG_tcfC&quot;</span><span style="color: #000000">, </span><span style="color: #795E26">into</span><span style="color: #000000">: &amp;demangledOutputSpan)</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">let</span><span style="color: #000000"> utf8 = </span><span style="color: #AF00DB">try</span><span style="color: #000000"> </span><span style="color: #795E26">UTF8Span</span><span style="color: #000000">(</span><span style="color: #795E26">validating</span><span style="color: #000000">: demangledOutputSpan.</span><span style="color: #001080">span</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">let</span><span style="color: #000000"> demangledString = </span><span style="color: #267F99">String</span><span style="color: #000000">(</span><span style="color: #795E26">copying</span><span style="color: #000000">: utf8)</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #795E26">print</span><span style="color: #000000">(demangledString) </span><span style="color: #008000">// &quot;Swift.String.init(cString: Swift.UnsafePointer&lt;Swift.Int8&gt;) -&gt; Swift.String&quot;</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">catch</span><span style="color: #000000"> DemanglingError.</span><span style="color: #795E26">truncated</span><span style="color: #000000">(</span><span style="color: #0000FF">let</span><span style="color: #000000"> requiredBufferSize) {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// Handle truncation - need a buffer of size requiredBufferSize</span></span>
<span class="line"><span style="color: #000000">} </span><span style="color: #AF00DB">catch</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #008000">// Handle invalid symbol</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Caution:</p>



<ul class="wp-block-list">
<li>While the format of <em>mangled</em> symbol names are pretty much guaranteed to not change, because they&#8217;re part of the Swift ABI which froze circa Swift 5, the format of <em>unmangled</em> symbol names are not standardised and this new function explicitly reserves the right to change its output format in future.</li>
</ul>



<h2 class="wp-block-heading">Software Bill of Materials Generation</h2>



<p class="wp-block-paragraph"><a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0509-swift-sboms-via-swiftpm.md" data-wpel-link="external" target="_blank" rel="external noopener">SE-509</a></p>



<p class="wp-block-paragraph">This adds a <code>swift package generate-sbom</code> command and a <code>--sbom-spec</code> flag to <code>swift build</code>, that both generate a &#8216;bill of materials&#8217; &#8211; a JSON file describing all the dependencies (recursively) of the package.  It can be used for auditing and to aid in build reproducibility, among other things.</p>



<p class="wp-block-paragraph">You can find <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0509-swift-sboms-via-swiftpm.md#detailed-design" data-wpel-link="external" target="_blank" rel="external noopener">examples of these JSON outputs in the proposal</a>.</p>



<p class="wp-block-paragraph">The initial implementation, in Swift 6.4, is pretty spartan, and doesn&#8217;t provide much more than what you can already see in <code>Package.resolved</code>.  But there&#8217;s <a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0509-swift-sboms-via-swiftpm.md#future-features" data-wpel-link="external" target="_blank" rel="external noopener">a long list of potential enhancements</a> which will make this feature more useful.</p>


<ol class="wp-block-footnotes"><li id="3a041e14-bcbd-482c-8579-e044ad71c434">Technically <code>Identifiable</code> doesn&#8217;t require the <code>id</code> property to be O(1), so it isn&#8217;t formally valid to conform <em>all</em> <code>Identifiable</code>s.  But in practice the function of <code>Identifiable</code> is often to short-cut an otherwise expensive equality operation, and the ID is typically a simple value type (e.g. number or string), so comparison usually <em>is</em> O(1). <a href="#3a041e14-bcbd-482c-8579-e044ad71c434-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/whats-coming-in-swift-6-4/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">9117</post-id>	</item>
		<item>
		<title>Claude Says No</title>
		<link>https://wadetregaskis.com/claude-says-no/</link>
					<comments>https://wadetregaskis.com/claude-says-no/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 08 May 2026 00:45:14 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Chromium]]></category>
		<category><![CDATA[Claude]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Snafu]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=9086</guid>

					<description><![CDATA[I strongly suspect Claude&#8217;s Mac app is written by Claude. That&#8217;s not a compliment. There&#8217;s the non-native GUI, though of course that doesn&#8217;t tell you much in this day and age. It&#8217;s obviously just Chromium or something similar (hell, you can right-click on some things and they still have the &#8220;Inspect Element&#8221; contextual menu item,&#8230; <a class="read-more-link" href="https://wadetregaskis.com/claude-says-no/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I strongly suspect Claude&#8217;s Mac app is written by Claude.</p>



<p class="wp-block-paragraph">That&#8217;s not a compliment.</p>



<p class="wp-block-paragraph">There&#8217;s the non-native GUI, though of course that doesn&#8217;t tell you much in this day and age.  It&#8217;s obviously just Chromium or something similar (hell, you can right-click on some things and they still have the &#8220;Inspect Element&#8221; contextual menu item, which does indeed open a very Chrome-looking web inspector).</p>



<p class="wp-block-paragraph">There&#8217;s its general everyday bugginess &#8211; it frequently resets the scroll position of conversations to some arbitrary point miles back in time, for example.  Or just abruptly removes focus from the text field while you&#8217;re in the middle of typing (doesn&#8217;t move it anywhere else, just defocuses).  It smells, in a nutshell.</p>



<p class="wp-block-paragraph">But the &#8220;vibe coding&#8221; stench really wafts in when you consider that [cynically] their most important user flow &#8211; the upsell &#8211; doesn&#8217;t even work.</p>



<p class="wp-block-paragraph">I hit my usage limit for the week.  So, I went into Settings, Billing, etc and upgraded to the &#8220;Max&#8221; level.  I&#8217;d been thinking about this anyway, so I didn&#8217;t have any animosity around it &#8211; I&#8217;d just been wondering if it was worth it; if I&#8217;d use the extra quota.</p>



<p class="wp-block-paragraph">Having done that successfully, I exit Settings, and go to resume my work &#8211; only to see my Code conversations are all still claiming I&#8217;ve hit the usage limit.  And the input text field is disabled.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="1073" height="160" src="https://wadetregaskis.com/wp-content/uploads/2026/05/claude-falsely-claiming-ive-reached-my-usage-limit.webp" alt="Screenshot of the text entry area of a Claude chat, showing that the text field is disabled and above it is a banner reading &quot;Usage limit reached - resets at 11:00 PM&quot;" class="wp-image-9087" srcset="https://wadetregaskis.com/wp-content/uploads/2026/05/claude-falsely-claiming-ive-reached-my-usage-limit.webp 1073w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-falsely-claiming-ive-reached-my-usage-limit-256x38.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-falsely-claiming-ive-reached-my-usage-limit-1024x153.webp 1024w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-falsely-claiming-ive-reached-my-usage-limit-768x115.webp 768w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-falsely-claiming-ive-reached-my-usage-limit@2x.webp 2146w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-falsely-claiming-ive-reached-my-usage-limit-256x38@2x.webp 512w" sizes="auto, (max-width: 1073px) 100vw, 1073px" /></figure>
</div>


<p class="wp-block-paragraph">Ugh.  Great.  Bugs.</p>



<p class="wp-block-paragraph">I go back into Settings to double-check &#8211; but no, I <em>have</em> in fact upgraded my account and do have plenty of quota left.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="988" height="417" src="https://wadetregaskis.com/wp-content/uploads/2026/05/claude-usage-at-zero-after-upgrading-plan.webp" alt="Screenshot of the usage / quota section of the Claude app's Settings, showing zero usage in all categories" class="wp-image-9088" srcset="https://wadetregaskis.com/wp-content/uploads/2026/05/claude-usage-at-zero-after-upgrading-plan.webp 988w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-usage-at-zero-after-upgrading-plan-256x108.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-usage-at-zero-after-upgrading-plan-768x324.webp 768w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-usage-at-zero-after-upgrading-plan@2x.webp 1976w, https://wadetregaskis.com/wp-content/uploads/2026/05/claude-usage-at-zero-after-upgrading-plan-256x108@2x.webp 512w" sizes="auto, (max-width: 988px) 100vw, 988px" /></figure>
</div>


<p class="wp-block-paragraph">I restart the app, of course, but that doesn&#8217;t help.  I click a whole bunch of buttons &#8211; I won&#8217;t bore you with the details, suffice to say there is <em>no</em> way to get the damn app to realise I have my entire quota left.</p>



<p class="wp-block-paragraph">So I go to the Help menu and click the promisingly-titled &#8220;Get Support&#8221;.  Which merely opens a page on Claude&#8217;s website, &#8220;<a href="https://support.claude.com/en/articles/9015913-how-to-get-support" data-wpel-link="external" target="_blank" rel="external noopener">How to get support</a>&#8220;.  Pro tip:  if your product or website needs such a page, you&#8217;ve already failed at customer support.</p>



<p class="wp-block-paragraph">That page tells me to go <em>back</em> to the app because the <em>only</em> avenue I have for communicating with them &#8211; even just to report a bug in their own damn app &#8211; is to use their support robot (via an incredibly obtuse GUI route &#8211; when your app has a Help menu, like <em>every app</em>, and it even includes a &#8220;Get Support&#8221; menu item, then <em>why the fuck</em> does it not just open your in-app support; why do you require an easter egg hunt through your GUI?!  Oh, wait, it&#8217;s because <em>this is not a native app and it bloody well shows</em>.</p>



<p class="wp-block-paragraph">Okay.</p>



<p class="wp-block-paragraph">Fine.</p>



<p class="wp-block-paragraph">Let&#8217;s just do this.</p>



<p class="wp-block-paragraph">I follow their ridiculous instructions on how to &#8216;get support&#8217;, but then I get this.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="444" height="748" src="https://wadetregaskis.com/wp-content/uploads/2026/05/claudes-useless-customer-support-robot.webp" alt="Screenshot of &quot;Fin&quot;, Claude's AI Support agent, refusing to let me talk to it (there is no input field, despite it explicitly saying it's awaiting my input)" class="wp-image-9085" srcset="https://wadetregaskis.com/wp-content/uploads/2026/05/claudes-useless-customer-support-robot.webp 444w, https://wadetregaskis.com/wp-content/uploads/2026/05/claudes-useless-customer-support-robot-152x256.webp 152w, https://wadetregaskis.com/wp-content/uploads/2026/05/claudes-useless-customer-support-robot@2x.webp 888w, https://wadetregaskis.com/wp-content/uploads/2026/05/claudes-useless-customer-support-robot-152x256@2x.webp 304w" sizes="auto, (max-width: 444px) 100vw, 444px" /></figure>
</div>


<p class="wp-block-paragraph">There&#8217;s something wrong with this picture.  Look carefully, it might be hard to miss.</p>



<p class="wp-block-paragraph">How the fuck am I supposed to talk to their bot?  There&#8217;s no text input field.</p>



<p class="wp-block-paragraph">This problem also persists across app relaunches.</p>



<p class="wp-block-paragraph">They had no trouble taking my money &#8211; they billed me for the upgraded plan within seconds &#8211; but they seem to have trouble actually delivering the service I paid for.</p>



<p class="wp-block-paragraph">And this is what&#8217;s considered state of the art today?</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/claude-says-no/feed/</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/05/claude-being-obstinate-1.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">9086</post-id>	</item>
		<item>
		<title>Asus ProArt PA32QCV</title>
		<link>https://wadetregaskis.com/asus-proart-pa32qcv/</link>
					<comments>https://wadetregaskis.com/asus-proart-pa32qcv/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 01 May 2026 20:38:54 +0000</pubDate>
				<category><![CDATA[Reviews]]></category>
		<category><![CDATA[5k display]]></category>
		<category><![CDATA[6k display]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Asus ProArt 6K]]></category>
		<category><![CDATA[HDR]]></category>
		<category><![CDATA[LG UltraFine 5K]]></category>
		<category><![CDATA[PA32QCV]]></category>
		<category><![CDATA[SDR]]></category>
		<category><![CDATA[VESA mount]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8959</guid>

					<description><![CDATA[I&#8217;ll cut to the chase: it&#8217;s a nice resolution upgrade from an Apple or LG 5k display. But that&#8217;s about it &#8211; in every other visual respect (brightness, contrast, etc) it&#8217;s basically the same or marginally worse (see Matte-ugly). Though the built-in KVM is a nice addition. Resolution The extra resolution is significant &#8211; it&#8230; <a class="read-more-link" href="https://wadetregaskis.com/asus-proart-pa32qcv/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I&#8217;ll cut to the chase: it&#8217;s a nice resolution upgrade from an Apple or LG 5k display. But that&#8217;s about it &#8211; in every other visual respect (brightness, contrast, etc) it&#8217;s basically the same or marginally worse (see <a href="#Matte-ugly">Matte-ugly</a>). Though the built-in KVM is a nice addition.</p>



<h3 class="wp-block-heading">Resolution</h3>



<p class="wp-block-paragraph">The extra resolution is significant &#8211; it <em>is</em> 38% more pixels &#8211; and welcome, but it&#8217;s not revolutionary.  It feels like what <em>should</em> have just been the natural progression and not a big deal &#8211; in the same way we started with 9&#8243; sub-VGA displays and have over decades worked our way up to bigger and higher-resolution ones.</p>



<p class="wp-block-paragraph">And in that vein, the prospect of downgrading back to a 5k display is immediately deeply unappealing (pay attention, Apple 😠). The notion of going down to a mere 4k display is absurd (tempting as bright[er] OLEDs are<sup data-fn="916ab8be-1381-43f2-8c81-0e5100a8df6a" class="fn"><a href="#916ab8be-1381-43f2-8c81-0e5100a8df6a" id="916ab8be-1381-43f2-8c81-0e5100a8df6a-link">1</a></sup>).</p>



<h3 class="wp-block-heading">Dimness (née Brightness)</h3>



<p class="wp-block-paragraph">Even though it&#8217;s supposedly brighter than the LG UltraFine 5K it&#8217;s replacing for me &#8211; at least at peak, given its DisplayHDR 600 rating &#8211; it really isn&#8217;t. For everyday work (coding etc) I had the LG set at 50% brightness (so nominally 250 nits, given its 500 peak rating) but the equivalent brightness requires ~75% on the Asus. Which actually fits if you take <a href="https://www.bhphotovideo.com/lit_files/1239784.pdf" data-wpel-link="external" target="_blank" rel="external noopener">the Asus&#8217;s manual</a> (<em>not</em> <a href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/techspec/" data-wpel-link="external" target="_blank" rel="external noopener">its tech specs</a>) at its word of a 350 nominal max brightness, since 250 nits is roughly 75% of 350.</p>



<p class="wp-block-paragraph">So that&#8217;s disappointing.  The LG UltraFine 5K was <em>sort of</em> bright for its day, but that day was nearly a decade ago.  We now have cheap laptops with 1000-nit displays, so 500 is unequivocally dim now.</p>



<p class="wp-block-paragraph">Not that I expected much else &#8211; while <a href="https://wadetregaskis.com/6k-display-comparison/#f5a790c8-9793-46bf-800d-a9c1dbe707d2" data-wpel-link="internal">Asus can&#8217;t seem to agree with themselves over the actual peak brightness</a>, they never claim more than 400, so going in I suspected it would be dim.  What I didn&#8217;t expect was that the claims of <a href="https://www.pcworld.com/article/2873124/asus-proart-pa32qcv-review.html" data-wpel-link="external" target="_blank" rel="external noopener">multiple</a> <a href="https://www.tomshardware.com/monitors/asus-proart-pa32qcv-32-inch-6k-professional-monitor-review#:~:text=I%20measured%20almost%20650%20nits%20in%20my%20tests%2C%20and%20that%20was%20from%20both%20full%20field%20and%20window%20patterns" data-wpel-link="external" target="_blank" rel="external noopener">reviewers</a>, that it&#8217;s actually closer to 700, would turn out to be completely false.</p>



<p class="wp-block-paragraph">The <em>one</em> thing that makes a difference in practice, in the Asus&#8217;s favour over some older displays like the LG, is that it supports HDR mode.  So you <em>can</em> in practice get noticeably higher brightness in image &amp; video editing without having to blind yourself<sup data-fn="3a434a32-fb6d-4f48-a6b2-6a79e0efbd71" class="fn"><a href="#3a434a32-fb6d-4f48-a6b2-6a79e0efbd71" id="3a434a32-fb6d-4f48-a6b2-6a79e0efbd71-link">2</a></sup>.</p>



<h3 class="wp-block-heading">Matte-ugly</h3>



<p class="wp-block-paragraph">I do not like the matte finish.  Compared to the LG UltraFine 5K that I was previously using, the PA32QCV has lower overall contrast (it&#8217;s more washed-out looking), and visible &#8216;shimmer&#8217; or &#8216;sparkle&#8217; &#8211; basically fine luminance noise that changes as your viewing position changes (even just slightly). It makes the screen look a bit dirty, too.</p>



<p class="wp-block-paragraph">In a nutshell, contrast is lower than it should be, and text just doesn&#8217;t have <em>quite</em> the same clarity it does on other displays (like Apple&#8217;s, or the LG UltraFine 5k).</p>



<p class="wp-block-paragraph">I&#8217;ve not yet decided if it&#8217;s a deal-breaker… there&#8217;s no good glossy <a href="https://wadetregaskis.com/6k-display-comparison/" data-type="post" data-id="8747" data-wpel-link="internal">6k display options</a> currently. I&#8217;m <em>hoping</em> that I&#8217;ll just get used to it. But there&#8217;s no mistaking that the screen&#8217;s finish is notably worse than its predecessors and Apple contemporaries. <em>Especially</em> for bright content (folks using Dark Mode might not be affected so much).</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">A little digression:  I still [vaguely] remember when Apple introduced the first glossy screens (as an option, not the default) in 2006, and then the storm in a teacup when they made glossy the <em>default</em> (but matte still an option) in 2008 MacBooks (and glossy the <em>only</em> option for the 2008 Cinema Displays).  Back then, I was against the glossy displays &#8211; not zealously, but I didn&#8217;t understand why you&#8217;d want a display that was basically the same except for the notable addition of annoying reflections.</p>



<p class="wp-block-paragraph">In retrospect, I think the key difference was Retina. When your pixels are the size of boulders (pre-Retina), a bit of blurring or fine-resolution grain is largely irrelevant because it&#8217;s so small compared to the pixels themselves. Indeed, I can&#8217;t find <em>any</em> mention of sparkling or blur in contemporary writings of that time &#8211; all the discussion centres foremost on reflections and (sometimes) the possibility of higher macro-contrast (deeper blacks, primarily).</p>



<p class="wp-block-paragraph">But when your pixels are <em>also</em> small &#8211; approaching the size of the speckling &#8211; suddenly it matters, because an entire pixel can be obscured or corrupted by the matte finish&#8217;s &#8220;sparkles&#8221; or blur.  Your eyes (or brain) can no longer apply an optical &#8220;low pass filter&#8221; to ignore the matte&#8217;s effects.</p>



<p class="wp-block-paragraph">On the &#8216;upside&#8217;, as I age my eyes continue to degrade, so eventually I&#8217;ll no longer be able to see the sparkles. 😆😐😞</p>
</div></div>



<h3 class="wp-block-heading">VESA mounting</h3>



<p class="wp-block-paragraph">I was perplexed when I first opened the box, and found what looked like a proprietary mounting system, with only a stand included, not a VESA mount adapter.  The box contains basically no instructions or explanation of anything, and even the manual &#8211; dug up through <a href="https://www.manualslib.com/manual/3962648/Asus-Proart-Pa32qcv.html" data-wpel-link="external" target="_blank" rel="external noopener">manualslib</a> online because Asus&#8217;s website contains only broken links to it (though I later noticed that <a href="https://www.bhphotovideo.com/lit_files/1239784.pdf" data-wpel-link="external" target="_blank" rel="external noopener">B&amp;H host it too</a>) &#8211; has no real information on how to VESA mount the display, other than cryptically stating that you need a &#8220;VESA Wall Mount Adapter (sold separately)&#8221;.</p>



<p class="wp-block-paragraph">Thankfully, <a href="https://www.reddit.com/user/Itchy_Pin9813/" data-wpel-link="external" target="_blank" rel="external noopener">Itchy_Pin9813</a> on <a href="https://www.reddit.com/r/ASUS/comments/1kyxnpu/how_do_i_use_a_third_party_vesa_stand_with_my/" data-wpel-link="external" target="_blank" rel="external noopener">Reddit</a> had already figured it out &#8211; the four screws that <em>look</em> like they might be structural, are actually just weird placeholders.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="2048" height="1536" src="https://wadetregaskis.com/wp-content/uploads/2026/05/before-removing-the-built-in-screws.avif.avif" alt="" class="wp-image-8961" srcset="https://wadetregaskis.com/wp-content/uploads/2026/05/before-removing-the-built-in-screws-1024x768@2x.avif.avif 2048w, https://wadetregaskis.com/wp-content/uploads/2026/05/before-removing-the-built-in-screws-256x192.avif 256w, https://wadetregaskis.com/wp-content/uploads/2026/05/before-removing-the-built-in-screws-1024x768.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2026/05/before-removing-the-built-in-screws-768x576.avif 768w, https://wadetregaskis.com/wp-content/uploads/2026/05/before-removing-the-built-in-screws-256x192@2x.avif.avif 512w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>
</div>


<p class="wp-block-paragraph">You just remove them and then screw in any standard 100⨉100 VESA mount.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="2048" height="1536" src="https://wadetregaskis.com/wp-content/uploads/2026/05/after-attaching-a-vesa-mount-plate.avif.avif" alt="" class="wp-image-8962" srcset="https://wadetregaskis.com/wp-content/uploads/2026/05/after-attaching-a-vesa-mount-plate-1024x768@2x.avif.avif 2048w, https://wadetregaskis.com/wp-content/uploads/2026/05/after-attaching-a-vesa-mount-plate-256x192.avif 256w, https://wadetregaskis.com/wp-content/uploads/2026/05/after-attaching-a-vesa-mount-plate-1024x768.avif 1024w, https://wadetregaskis.com/wp-content/uploads/2026/05/after-attaching-a-vesa-mount-plate-768x576.avif 768w, https://wadetregaskis.com/wp-content/uploads/2026/05/after-attaching-a-vesa-mount-plate-256x192@2x.avif.avif 512w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>
</div>


<p class="wp-block-paragraph">However, be aware that the mounting point is recessed significantly. Though the plate itself fit in the recessed area just fine (as shown above), I was only <em>barely</em> able to get my mount plate attached to the arm itself, without the arm hitting the surrounding casing. I did notice that <a href="https://www.amazon.com/VG27AQ3A-Compatible-VG27AQM5A-VG277Q1A-VG27WQ3B/dp/B0FM81CJ6F" data-wpel-link="external" target="_blank" rel="external noopener">you can buy adapters specifically designed to address this design flaw</a>.</p>



<h3 class="wp-block-heading">BSOD</h3>



<p class="wp-block-paragraph">When the display detects no input video signal, it displays a <em>hideously</em>-coloured bright blue screen, reminiscent of Window&#8217;s Blue Screen of Death.</p>



<p class="wp-block-paragraph">Which would be fine &#8211; generally you won&#8217;t see that, unless something&#8217;s genuinely gone wrong with your cables or computer &#8211; except that it sometimes flashes into this mode when your Mac goes to sleep or wakes the display back up. It&#8217;s jarring and ugly.</p>



<p class="wp-block-paragraph">This isn&#8217;t the first display to have this design flaw, but I just cannot fathom how, after decades of experience in displays all around us in the real world, someone somewhere inside Asus <em>still</em> thought it&#8217;d be a good idea to do this instead of just displaying a black screen (optionally with calm grey &#8220;No Input Signal&#8221; text on it).</p>



<h3 class="wp-block-heading">KVM</h3>



<p class="wp-block-paragraph">Having a built-in KVM <em>should be nice</em> &#8211; I have my personal Mac and [sometimes] my work laptop connected, and previously I was manually moving the Thunderbolt cable between them (like a cave man! 😜).  Which wasn&#8217;t a big deal per se, but I do worry about frequently plugging and unplugging a Thunderbolt cable &#8211; those USB-C style connectors aren&#8217;t infinitely durable.  And Thunderbolt is a pretty demanding protocol, that I suspect doesn&#8217;t tolerate electrical flaws well.  And quality Thunderbolt replacement cables aren&#8217;t cheap.</p>



<p class="wp-block-paragraph">So, KVM, great!</p>



<p class="wp-block-paragraph">Except… the implementation in the Asus is pretty clunky. There&#8217;s a dedicated &#8220;Input Source Switch&#8221; button right on the front panel, which <em>should</em> be perfect &#8211; except it often doesn&#8217;t work. If your Mac(s) are set to turn the display off after some period of idleness, they&#8217;ll stop sending a video signal to the display, and the display then considers them non-existent. So the &#8220;Input Source Switch&#8221; button only ever works in the brief period after a previous switch, when the prior Mac is still sending a video signal to the display.</p>



<p class="wp-block-paragraph">Disabling display auto-off on all your computers is one option, but not a great one &#8211; sometimes I&#8217;m called away abruptly, potentially for many hours, and I don&#8217;t want the display sitting there wasting power and burning itself in. A screen saver might help with the burn-in aspect, at least, but not the power (and keep in mind this display is rated to about 50W (not counting USB &amp; Thunderbolt power), which is too much to waste).</p>



<p class="wp-block-paragraph">If you have your keyboard &amp; mouse connected through the display &#8211; in order to make intended use of the KVM functionality &#8211; then you also run into the problem that if the Mac has gone into screen off mode, the Asus display turns off all the USB devices too.  So you can&#8217;t wake your Mac from the keyboard or mouse.  Nor switch input sources.</p>



<p class="wp-block-paragraph">So, I&#8217;m having to resort to hitting the power button on my Mac Studio, and <em>opening my laptop</em> on the MacBook Pro, in order to wake them up.  Or, I discovered that you can &#8216;force&#8217; switch input sources &#8211; which will wake the connected Mac &#8211; through the display&#8217;s on-screen display.  But that&#8217;s quite a few clicks and nudges of its joystick &#8211; and only makes it more baffling and frustrating that the dedicated &#8220;Input Source Switch&#8221; button doesn&#8217;t just work.</p>



<p class="wp-block-paragraph">I wish there were a configuration option to (a) never power down the USB devices and (b) just tell the display that some inputs (Thunderbolt, Display Port, and/or HDMI) are <em>always</em> connected, whether it&#8217;s receiving a video signal currently or not.  Clearly the display <em>can</em> wake up connected Macs &#8211; it does so when you select their input source deep in its settings.</p>



<h3 class="wp-block-heading">Picture-in-Picture (PIP) / Picture-beside-Picture (PBP)</h3>



<p class="wp-block-paragraph">I&#8217;m not sure if I&#8217;d ultimately use these &#8211; I hate the PIP &#8220;feature&#8221; of YouTube and some Apple apps, for example, and don&#8217;t want to mess with display resolution and aspect ratios for any of my connected computers &#8211; but it turns out I can&#8217;t, anyway.</p>



<p class="wp-block-paragraph">The caveats are buried deep in the manual:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">To active this function [PIP / PBP], you need to do the following: turn off <strong>MediaSync</strong> / <strong>Dynamic Dimming</strong> and disable HDR on your device.</p>
</blockquote>



<p class="wp-block-paragraph">I don&#8217;t want MediaSync or Dynamic Dimming anyway (see the <a href="#Configuration_recommendations" data-type="internal" data-id="#Configuration-recommendations">Configuration recommendations</a> section below for why), but I <em>do</em> want HDR mode enabled.  Having to disable HDR mode is far too great a sacrifice, for a feature that&#8217;s arguably just a gimmick anyway.</p>



<h3 class="wp-block-heading">10-bit colour lies</h3>



<p class="wp-block-paragraph">The Asus (like most displays these days) is marketed as having 10-bit colour.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="1013" height="521" src="https://wadetregaskis.com/wp-content/uploads/2026/05/asus-pa32qcv-product-page-claiming-10-bit-colour-support.avif.avif" alt="Screenshot of part of the product page on Asus's website for the PA32QCV display, showing their claim that it supports 10-bit colour" class="wp-image-8984" srcset="https://wadetregaskis.com/wp-content/uploads/2026/05/asus-pa32qcv-product-page-claiming-10-bit-colour-support.avif 1013w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-pa32qcv-product-page-claiming-10-bit-colour-support-256x132.avif 256w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-pa32qcv-product-page-claiming-10-bit-colour-support-768x395.avif 768w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-pa32qcv-product-page-claiming-10-bit-colour-support@2x.avif 2026w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-pa32qcv-product-page-claiming-10-bit-colour-support-256x132@2x.avif.avif 512w" sizes="auto, (max-width: 1013px) 100vw, 1013px" /></figure>
</div>


<p class="wp-block-paragraph"><strong>But it doesn&#8217;t.</strong></p>



<p class="wp-block-paragraph">The lie is revealed only in the back pages of the user manual (the one that doesn&#8217;t come with it in the box, nor is accessible from Asus&#8217;s website).  This display, like so many others, is actually an 8-bit panel that uses FRC (Frame Rate Control) i.e. temporal dithering: the monitor accepts a 10-bit signal but can only set the actual pixels to 8-bit precision, so it oscillates back and forth between adjacent 8-bit values in order to approximate the desired 10-bit value, over time.</p>



<p class="wp-block-paragraph">I don&#8217;t actually know how much that matters &#8211; I can&#8217;t say I&#8217;ve had complaints about the LG UltraFine 5K w.r.t. banding or other 8-bit artefacts, and it was also an 8-bit display with FRC.</p>



<p class="wp-block-paragraph">Still, I&#8217;m not happy with Asus basically lying in their marketing material &#8211; and tech specs, which are <em>usually</em> one place you can get to the truth.</p>



<h3 class="wp-block-heading">Subtler effects</h3>



<p class="wp-block-paragraph">It might take more time to appreciate some of the more subtle differences, vs the LG UltraFine 5K at least.</p>



<h4 class="wp-block-heading">HDR Mode</h4>



<p class="wp-block-paragraph">The ability to use HDR mode &#8211; meaning I can set my &#8216;regular&#8217; GUI brightness to what&#8217;s comfortable <em>without</em> [artificially] limiting the brightness of imagery &#8211; might reveal itself as kind of a big deal, with further use, but since the additional brightness is pretty minor (in a daylight-lit room, at least) I don&#8217;t know yet.</p>



<h4 class="wp-block-heading">Colour accuracy</h4>



<p class="wp-block-paragraph">I also haven&#8217;t actually checked the colour accuracy yet.  Asus do include a basic printed calibration report in the box, from their factory calibration, which is nice and hopefully not just performative.  I do have a colour metre (an old ColorMunki), but going through the process is frankly frustrating and tedious, and I&#8217;m rarely actually happy with the results (more accurate is not the same as <em>better looking</em>).  I can at least say that the colour looks fine (once I dialled in better settings than the defaults &#8211; see below) and similar-enough to the other displays in my life that I have no complaints.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="4604" height="5666" src="https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report.avif" alt="" class="wp-image-8970" srcset="https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report.avif 4604w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report-208x256.avif 208w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report-832x1024.avif 832w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report-768x945.avif 768w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report-1664x2048.avif 1664w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report-208x256@2x.avif.avif 416w, https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-display-pa32qcv-example-factory-calibration-report-1664x2048@2x.avif.avif 3328w" sizes="auto, (max-width: 4604px) 100vw, 4604px" /></figure>
</div>


<h3 class="wp-block-heading">Configuration recommendations</h3>



<ul class="wp-block-list">
<li>In macOS Settings:
<ul class="wp-block-list">
<li>Enable &#8220;High Dynamic Range&#8221; for the display (in the &#8220;Displays&#8221; pane).  This does two things:
<ul class="wp-block-list">
<li>It enables macOS brightness control &#8211; the brightness slider appears in System Settings, and the keyboard shortcuts to control brightness will then work.<br><br>Note that, conversely, it basically prevents you adjusting the display&#8217;s brightness from the display&#8217;s own controls (you become limited to &#8220;MAX&#8221; and 250).</li>



<li>It allows HDR content to use the full luminance range of the display irrespective of the brightness setting. i.e. you can adjust the &#8220;regular&#8221; brightness of the GUI independent of the HDR image &amp; video brightness. Note, however, that there doesn&#8217;t seem to be a way to control the brightness of HDR content.<br><br>Keep in mind, though, that the PA32QCV is not a bright display. It&#8217;s rated to <em>up to</em> 600 nits, and even that&#8217;s probably only for small patches of highlights or for brief time periods, which is really quite low &#8211; it&#8217;s nothing like a &#8220;true&#8221; HDR display such as Apple&#8217;s MacBook Pro displays, Apple&#8217;s Pro Display XDR, or the <a href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-8k-pa32kcx/" data-wpel-link="external" target="_blank" rel="external noopener">Asus ProArt 8K</a>, that have <em>sustained</em> maximum brightness of at least 1,000 nits (2⨉ brighter), and generally peak much higher.  Even iPhones are brighter<sup data-fn="4f676ac3-d1e3-4bd9-a8fd-ea450633f0b0" class="fn"><a href="#4f676ac3-d1e3-4bd9-a8fd-ea450633f0b0" id="4f676ac3-d1e3-4bd9-a8fd-ea450633f0b0-link">3</a></sup> (and have <em>much</em> better dynamic range, being OLEDs).</li>
</ul>
</li>
</ul>
</li>



<li>In the display&#8217;s settings:
<ul class="wp-block-list">
<li><strong>Settings > Dynamic Dimming</strong> should be <em>OFF</em>. When it&#8217;s on the display adjusts the brightness over time in response to the image shown, but <em>very</em> slowly such that you can see it ramping up or down lazily after an average brightness change (no matter what you tweak its sub-settings to). It&#8217;s just <em>horrible</em> for video creation since it&#8217;s seriously messing with your luminance and animation. And I&#8217;m not even sure it&#8217;s a good idea when merely watching video, since the brightness transitions are noticeable and distracting.<br><br>If you never view animated content, then I suppose leaving it on <em>could</em> be beneficial since its purpose is ostensibly to give you greater static range &#8211; when the screen overall is dim, such as editing a dark photo, it will reduce the backlight in order to darken the blacks, while conversely in a bright image it&#8217;ll boost the backlight to give you maximum brightness (but at the expense of washing out the blacks completely).<br><br>But, it really doesn&#8217;t do much.  Yes, I can see the effect &#8211; it does make the blacks a little bit darker when the image is overall quite dark &#8211; but it&#8217;s very subtle and not remotely worth the visual artefacts it introduces.</li>



<li><strong>Palette > Brightness</strong> must be set to &#8220;MAX&#8221; when macOS is using HDR mode, otherwise you&#8217;ll be limited to 250 nits even for HDR content! If macOS is not set to HDR mode then it sets the brightness in nits, from 0 to 400.<br><br>It&#8217;s pleasing to see a scale that&#8217;s in real units, not just some arbitrary scale like 0 to 100%. Or at least, I&#8217;m assuming that&#8217;s the case &#8211; that the scale goes to 400, and otherwise odd, arbitrary number, and that the display&#8217;s nominal peak [SDR] brightness is 400<sup data-fn="9ff621ae-0e0c-4594-845f-390ba6ebf43d" class="fn"><a href="#9ff621ae-0e0c-4594-845f-390ba6ebf43d" id="9ff621ae-0e0c-4594-845f-390ba6ebf43d-link">4</a></sup> seems like an unlikely coincidence.</li>



<li><strong>Palette &gt; Black Level &gt; Signal</strong> should be left at its default, 50.  Changing this basically changes the luminance curve of the display &#8211; lowering it pulls down the brightness, crushing the shadows in particular, while raising it increases the brightness, washing out the shadows.  It has no apparent effect on actual black levels (nor, surprisingly, does its peer &#8220;Backlight&#8221; setting, which seems strange because surely that&#8217;s the point of it?).</li>



<li>If you&#8217;re <em>creating</em> HDR content, in the display&#8217;s settings, set <strong>Preset > HDR</strong> to &#8220;PQ Clip&#8221;.<br><br>If you&#8217;re <em>viewing</em> HDR content, use &#8220;PQ Optimized&#8221;.<br><br>Yes, this might be something you have to change frequently, because there&#8217;s no happy medium. 😔<br><br>Under the default setting, &#8220;PQ Optimized&#8221;, the display futzes with the image to make the display&#8217;s brightness limit less noticeable &#8211; it &#8220;smoothes&#8221; out the approach to the maximum brightness by making the too-bright parts darker (to prevent clipping) <em>and</em> the nearly-too-bright parts <em>brighter</em>. This provides a <em>pleasing</em> but highly inaccurate effect &#8211; you don&#8217;t see stark clipping as easily, and the image overall looks bright, but you&#8217;re seriously changing the localised luminance of the image. If you edit a photo or video this way and then put it on another display, you may be dismayed to find it looks <em>completely</em> different, luminance- and contrast-wise.<br><br>This is a consequence, I infer, of how macOS renders HDR content. In SDR mode, macOS just directly maps the input image&#8217;s dynamic range to the display&#8217;s &#8211; 100% in the image goes to the display as 100%. But in HDR mode it seems like it&#8217;s basically ignoring your display&#8217;s capabilities and working in <em>absolute</em> brightness. So if the input image says it is 2,000 nits, macOS emits pixels with that nominal brightness. Which may be <em>way</em> beyond what the display can handle, so they just get clipped to its max brightness (or artificially adjusted by the display &#8211; as in &#8220;PQ Optimized&#8221; and &#8220;PQ Basic&#8221; modes).<br><br>And (for completeness) the &#8220;PQ Basic&#8221; setting is, I think, doing just the darkening part of &#8220;PQ Optimized&#8221;, which in a nutshell means it looks like &#8220;PQ Optimized&#8221; but dimmer overall. I&#8217;m not sure what use that is.</li>



<li><strong>Settings &gt; PowerSaving</strong> can be set to &#8220;Deep Level&#8221; (the default, for &#8220;Energy Saver&#8221; mode) <em>iff</em> you have macOS set to HDR mode.  Otherwise, it limits the maximum brightness severely.<br><br>Of course, I&#8217;m not yet sure what &#8220;Deep Level&#8221; <em>does</em> in HDR mode &#8211; possibly nothing.  But I&#8217;m hoping it just means the display uses less power when not actually active.</li>
</ul>
</li>
</ul>


<ol class="wp-block-footnotes"><li id="916ab8be-1381-43f2-8c81-0e5100a8df6a">This ties into my choice to get this 6k display, here and now.  I agonised over the decision for a long time.  I <em>was</em> pretty much resigned to just paying some obscene amount of money for the Apple Pro Display XDR 2 &#8211; on the assumption that it&#8217;d be a nice modest upgrade with more backlight zones and a brightness boost, which turned out to be correct but alas not the whole story &#8211; but once Apple publicised that they were killing the XDR entirely, <a href="https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/" data-type="post" data-id="8738" data-wpel-link="internal">it left me in the doldrums</a>.<br><br>I considered many options &#8211; including buying no-name-brand ones from China &#8211; but ultimately whittled it down to two possibilities:  the Asus ProArt 8K, or the 6K.  The Asus ProArt 8K is Apple-level expensive, and at 32&#8243; its pixel density is far too high to actually reap noticeable benefit from the extra pixels over the 6K, but it otherwise checks the boxes.  I spent a long time trying to convince myself it wasn&#8217;t insane to spend $9,000 on a display &#8211; keeping in mind that the original Apple Cinema Display was $4k at time of release in 1999, which is nearly $8k in 2026 dollars, and the Sony Trinitron displays were <em>thousands</em> of dollars too, in the 1990s… I also tried to reason that a display <em>should</em> last basically forever, and remain useful for decades, so what&#8217;s $9k over the rest of my life? 😅<br><br>But in the end I thought… why?  Even that $9k 8K display isn&#8217;t the best at everything.  It&#8217;s not the brightest, it doesn&#8217;t have the biggest colour gamut, it doesn&#8217;t have the best contrast ratio &#8211; it&#8217;s not even the prettiest… if I&#8217;m going to spend eye-watering amounts of money, I want to at least feel like it&#8217;s <em>worth it</em>.<br><br>So, I went with the cheapest option instead, on the assumption that I&#8217;ll revisit my display situation in a few more years. <a href="#916ab8be-1381-43f2-8c81-0e5100a8df6a-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="3a434a32-fb6d-4f48-a6b2-6a79e0efbd71">On &#8220;non-HDR&#8221; displays &#8211; specifically, where macOS doesn&#8217;t let you use the &#8220;HDR&#8221; option in the display settings &#8211; your images &amp; video can only be displayed as bright as the current brightness setting &#8211; which is typically <em>not</em> the maximum brightness the display can manage, because if you raise the display to maximum brightness in order to get the full dynamic range available, then all your regular windows &#8211; white-backed webpages, TextEdit &amp; Xcode documents, etc &#8211; become blindingly bright. <a href="#3a434a32-fb6d-4f48-a6b2-6a79e0efbd71-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="4f676ac3-d1e3-4bd9-a8fd-ea450633f0b0">Well, maybe… <a href="https://www.apple.com/iphone-17-pro/specs/#:~:text=3000%C2%A0nits%20peak%20brightness%20(outdoor)" data-wpel-link="external" target="_blank" rel="external noopener">the iPhone 17 Pro might be <em>rated</em> at [up to] 3,000 nits</a>, but in reality it can&#8217;t even handle the display being on <em>at all</em> sometimes, such as if exposed to sunlight.  I have a torch (flashlight, Americans), a <a href="https://www.youtube.com/watch?v=KHgstunB5fE" data-wpel-link="external" target="_blank" rel="external noopener">Google Firesword</a>, that&#8217;s spec&#8217;d as 3,000 lumens &#8211; just 875 nits &#8211; and it&#8217;s <em>way</em> brighter than the iPhone screen ever is.  <em>Way</em> brighter.<br><br>Yes, there&#8217;s a significant difference in emitter area between a torch and an iPhone&#8217;s display, but even just considering how much it lightens the room it&#8217;s in, the Firesword <em>easily</em> wins against any iPhone.  And any display I&#8217;ve owned. <a href="#4f676ac3-d1e3-4bd9-a8fd-ea450633f0b0-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="9ff621ae-0e0c-4594-845f-390ba6ebf43d">Well, maybe.  As noted, while <a href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/techspec/" data-wpel-link="external" target="_blank" rel="external noopener">the tech specs</a> claim 400, <a href="https://www.bhphotovideo.com/lit_files/1239784.pdf" data-wpel-link="external" target="_blank" rel="external noopener">the manual</a> says 350, and it&#8217;s my guess &#8211; based on comparison with other displays and referencing their rated peak brightnesses the the truth is much closer to 350 than 400. <a href="#9ff621ae-0e0c-4594-845f-390ba6ebf43d-link" aria-label="Jump to footnote reference 4">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/asus-proart-pa32qcv/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/05/asus-proart-6k-pa32qcv-2048x2048.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8959</post-id>	</item>
		<item>
		<title>SSH waits for background jobs to exit only in non-pseudo-terminal mode</title>
		<link>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/</link>
					<comments>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 22 Mar 2026 23:40:42 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[pseudo-terminal]]></category>
		<category><![CDATA[SSH]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8867</guid>

					<description><![CDATA[Ugh, this one caused me days of head-scratching. If you run that command sequence, you&#8217;ll find that it makes the SSH connection and then exits immediately &#8211; and it kills the background job (sleep 15) as it exits. Which I find intuitive if only because that&#8217;s obviously how it&#8217;s always worked. But now run it&#8230; <a class="read-more-link" href="https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Ugh, this one caused me days of head-scratching.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$ ssh somehost
somehost$ sleep 15 &amp;
somehost$ exit</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">$ </span><span style="color: #A31515">ssh</span><span style="color: #000000"> </span><span style="color: #A31515">somehost</span></span>
<span class="line"><span style="color: #000000">somehost$ </span><span style="color: #A31515">sleep</span><span style="color: #000000"> </span><span style="color: #098658">15</span><span style="color: #000000"> &amp;</span></span>
<span class="line"><span style="color: #000000">somehost$ </span><span style="color: #A31515">exit</span></span></code></pre></div>



<p class="wp-block-paragraph">If you run that command sequence, you&#8217;ll find that it makes the SSH connection and then exits immediately &#8211; and it kills the background job (<code>sleep 15</code>) as it exits.  Which I find intuitive if only because that&#8217;s obviously how it&#8217;s always worked.</p>



<p class="wp-block-paragraph">But now run it this way:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>$ ssh somehost 'sleep 15 &amp;'</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">$ </span><span style="color: #A31515">ssh</span><span style="color: #000000"> </span><span style="color: #A31515">somehost</span><span style="color: #000000"> </span><span style="color: #A31515">&#39;sleep 15 &amp;&#39;</span></span></code></pre></div>



<p class="wp-block-paragraph">Now it makes the SSH connection, <em>waits fifteen seconds</em>, and only <em>then</em> exits.</p>



<p class="wp-block-paragraph">This subtle difference matters a lot if you&#8217;re e.g. writing an automation system that happens to sometimes spawn background jobs on remote machines via SSH, since it can lead to SSH commands hanging (potentially forever, depending on the nature of the background jobs).</p>



<p class="wp-block-paragraph">And it&#8217;s <em>very</em> hard to debug, because no matter what debugging aids you put into your SSH session&#8217;s remote command &#8211; e.g. an explicit <code>exit 0</code> at the end, <code>set -xv</code>, or <code>echo 'Okay, going away now!'</code> as the last command &#8211; it&#8217;ll still just hang instead of completing.</p>



<p class="wp-block-paragraph">Turns out this is because when run <em>without</em> a remote command, SSH implicitly creates a pseudo-terminal.  And the <em>pseudo-terminal</em> provides the behaviour of killing all subprocesses (including un-detached background jobs) when the main process ends.</p>



<p class="wp-block-paragraph">When you run SSH <em>with</em> a remote command, SSH does <em>not</em> create a pseudo-terminal.  I&#8217;m guessing technically what it&#8217;s doing is waiting for the remote side to close the connection, which the remote side (sshd) won&#8217;t do until <em>all</em> child processes have exited (or at least closed their stdin, stdout, and stderr?).</p>



<p class="wp-block-paragraph">One workaround is to use the <code>-t</code> argument to SSH, to force creation of a pseudo-terminal.  The downside is that remote programs may then assume an interactive session, so they may prompt for input in cases where they wouldn&#8217;t otherwise.  That could cause misalignment between what you write through to the remote command and what it&#8217;s expecting input for, or may cause a hang (if you don&#8217;t write anything but keep the remote side&#8217;s stdin open), or may cause the remote command to abort because it detects the end of stdin.</p>



<p class="wp-block-paragraph">Another option is to manually kill all background jobs before exiting the main command, with e.g.:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>jobs -p | egrep '^\d' | xargs kill</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #795E26">jobs</span><span style="color: #000000"> </span><span style="color: #0000FF">-p</span><span style="color: #000000"> | egrep </span><span style="color: #A31515">&#39;^\d&#39;</span><span style="color: #000000"> | xargs </span><span style="color: #A31515">kill</span></span></code></pre></div>



<p class="wp-block-paragraph">…or just kill <em>all</em> subprocesses:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">pkill </span><span style="color: #0000FF">-P</span><span style="color: #000000"> </span><span style="color: #0000FF">$$</span></span></code></pre></div>



<p class="wp-block-paragraph">Or, if you can precisely control where you create background jobs (<em>and</em> are confident they don&#8217;t spawn background jobs recursively), you can do:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>some background command &amp;
BACKGROUND_PID=$!

… # Rest of script.

kill ${BACKGROUND_PID}
# *Now* we can exit without hanging.</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">some </span><span style="color: #A31515">background</span><span style="color: #000000"> </span><span style="color: #A31515">command</span><span style="color: #000000"> &amp;</span></span>
<span class="line"><span style="color: #001080">BACKGROUND_PID</span><span style="color: #000000">=</span><span style="color: #0000FF">$!</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">… </span><span style="color: #008000"># Rest of script.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #795E26">kill</span><span style="color: #000000"> ${BACKGROUND_PID}</span></span>
<span class="line"><span style="color: #008000"># *Now* we can exit without hanging.</span></span></code></pre></div>



<p class="wp-block-paragraph">Just beware with this more precise approach that it&#8217;s more fragile &#8211; subprocesses could get spawned in ways you didn&#8217;t anticipate (if not now, then maybe in future versions of your code / production environment), and that sometimes the PID returned is effectively invalid because it&#8217;s merely the PID of some transient subprocess anyway (not the <em>long-lived</em> subprocess(es)).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/ssh-waits-for-background-jobs-to-exit-only-in-non-pseudo-terminal-mode/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8867</post-id>	</item>
		<item>
		<title>&#8220;\r\n&#8221; is one Character in Swift</title>
		<link>https://wadetregaskis.com/rn-is-one-character-in-swift/</link>
					<comments>https://wadetregaskis.com/rn-is-one-character-in-swift/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 17 Mar 2026 01:43:49 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[isNewline]]></category>
		<category><![CDATA[Swift]]></category>
		<category><![CDATA[Unicode]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8790</guid>

					<description><![CDATA[From the department of “how did I not realise this sooner?!”: Yes, Swift treats the two bytes &#8220;\r\n&#8221; as a single character.  This is actually super convenient a lot of the time, because it means algorithms that look for line breaks with isNewline just work, even on “Windows”-style text.  Otherwise, you’d have to explicitly look&#8230; <a class="read-more-link" href="https://wadetregaskis.com/rn-is-one-character-in-swift/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">From the department of “how did I not realise this sooner?!”:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">  </span><span style="color: #098658">1</span><span style="color: #000000">&gt; </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\r</span><span style="color: #A31515">&quot;</span><span style="color: #000000">.</span><span style="color: #001080">count</span></span>
<span class="line"><span style="color: #000000">$R0: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">1</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #098658">2</span><span style="color: #000000">&gt; </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\n</span><span style="color: #A31515">&quot;</span><span style="color: #000000">.</span><span style="color: #001080">count</span><span style="color: #000000"> </span></span>
<span class="line"><span style="color: #000000">$R1: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">1</span></span>
<span class="line"><span style="color: #000000">  </span><span style="color: #098658">3</span><span style="color: #000000">&gt; </span><span style="color: #A31515">&quot;</span><span style="color: #EE0000">\r\n</span><span style="color: #A31515">&quot;</span><span style="color: #000000">.</span><span style="color: #001080">count</span><span style="color: #000000"> </span></span>
<span class="line"><span style="color: #000000">$R2: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">1</span></span></code></pre></div>



<p class="wp-block-paragraph">Yes, Swift treats the two bytes &#8220;\r\n&#8221; as a <em>single</em> character.  This is actually super convenient a lot of the time, because it means algorithms that look for line breaks with <code>isNewline</code> just work, even on “Windows”-style text.  Otherwise, you’d have to explicitly look for two <code>isNewline</code> characters in sequence <em>and</em> check if they are exactly &#8220;\r\n&#8221;.</p>



<p class="wp-block-paragraph">But it does lead to some potentially surprising side-effects, like:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000"> </span><span style="color: #098658">4</span><span style="color: #000000">&gt; x.</span><span style="color: #001080">unicodeScalars</span><span style="color: #000000">.</span><span style="color: #001080">count</span></span>
<span class="line"><span style="color: #000000">$R3: </span><span style="color: #267F99">Int</span><span style="color: #000000"> = </span><span style="color: #098658">2</span></span>
<span class="line"><span style="color: #000000"> </span><span style="color: #098658">5</span><span style="color: #000000">&gt; </span><span style="color: #267F99">Array</span><span style="color: #000000">(x.</span><span style="color: #001080">unicodeScalars</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">$R4: &#91;</span><span style="color: #267F99">String</span><span style="color: #000000">.</span><span style="color: #267F99">UnicodeScalarView</span><span style="color: #000000">.</span><span style="color: #267F99">Element</span><span style="color: #000000">&#93; = </span><span style="color: #098658">2</span><span style="color: #000000"> values {</span></span>
<span class="line"><span style="color: #000000">  &#91;</span><span style="color: #098658">0</span><span style="color: #000000">&#93; = U&#39;\r&#39;</span></span>
<span class="line"><span style="color: #000000">  &#91;</span><span style="color: #098658">1</span><span style="color: #000000">&#93; = U&#39;\n&#39;</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">This isn’t surprising if you’re pretty familiar with how Unicode actually works &#8211; starting with the difference between graphemes (approximately what Swift calls a Character) and &#8220;scalars&#8221;, but I suspect it’ll catch some people out.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/rn-is-one-character-in-swift/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8790</post-id>	</item>
		<item>
		<title>6k display comparison</title>
		<link>https://wadetregaskis.com/6k-display-comparison/</link>
					<comments>https://wadetregaskis.com/6k-display-comparison/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 07 Mar 2026 23:33:28 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[32U990A-S]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Dell]]></category>
		<category><![CDATA[LG]]></category>
		<category><![CDATA[PA32QCV]]></category>
		<category><![CDATA[Pro Display XDR]]></category>
		<category><![CDATA[U3224KB]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8747</guid>

					<description><![CDATA[It&#8217;s very clear that Apple were going for peak brightness above all else. Nobody else has even tried to make a bright 6k display &#8211; in fact, every non-Apple 6k display is outright dim by modern display standards &#8211; they&#8217;re barely brighter than the original 5k display in the 2015 iMac! For the price of&#8230; <a class="read-more-link" href="https://wadetregaskis.com/6k-display-comparison/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">It&#8217;s very clear that Apple were going for peak brightness above all else. Nobody else has even tried to make a bright 6k display &#8211; in fact, every non-Apple 6k display is outright <em>dim</em> by modern display standards &#8211; they&#8217;re barely brighter than the original 5k display in the 2015 iMac!<sup data-fn="a041ec8b-df15-4e40-85ce-8259991341aa" class="fn"><a href="#a041ec8b-df15-4e40-85ce-8259991341aa" id="a041ec8b-df15-4e40-85ce-8259991341aa-link">1</a></sup></p>



<p class="wp-block-paragraph">For the price of <em>one</em> Apple Pro Display XDR you can get <em>five</em> Asus ProArt 6k displays.  And it&#8217;s worth noting that the 2nd-hand price for the XDR has risen dramatically since it was discontinued, with some &#8216;retailing&#8217; on eBay for <em>more</em> than their original purchase price!  So forget about the 2nd-hand market.</p>



<p class="wp-block-paragraph">I suspect there&#8217;s only three 6k panel models in existence &#8211; the one used by Apple, the AUO one used by Asus<sup data-fn="08a8dafe-81b7-4c14-94ec-907400f178dc" class="fn"><a href="#08a8dafe-81b7-4c14-94ec-907400f178dc" id="08a8dafe-81b7-4c14-94ec-907400f178dc-link">2</a></sup>, Acer, &amp; ALOGIC, and the LG one used by LG, Dell, &amp; Kuycon.</p>



<p class="wp-block-paragraph">It&#8217;s strange to me that Dell haven&#8217;t dropped the price of their 6k display given that LG are offering the same panel in a much svelter package for 33% less (and you can get the very similar Asus display for 56% less!).</p>



<figure class="wp-block-table is-style-stripes"><table class="has-fixed-layout"><thead><tr><th></th><th class="has-text-align-center" data-align="center">Pro Display XDR</th><th class="has-text-align-center" data-align="center"><a href="https://www.lg.com/us/monitors/lg-32u990a-s-ultrafine-monitor" data-wpel-link="external" target="_blank" rel="external noopener">LG UltraFine™evo 6K Nano IPS Black Monitor with Thunderbolt™ 5 (32U990A-S)</a></th><th class="has-text-align-center" data-align="center"><a href="https://www.dell.com/en-us/shop/dell-ultrasharp-32-6k-monitor-u3224kb/apd/210-bhbz/monitors-monitor-accessories" data-wpel-link="external" target="_blank" rel="external noopener">Dell UltraSharp 32 6K (U3224KB)</a></th><th class="has-text-align-center" data-align="center"><a href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/" data-wpel-link="external" target="_blank" rel="external noopener">Asus ProArt Display 6K (PA32QCV)</a></th><th class="has-text-align-center" data-align="center"><a href="https://store.acer.com/en-sg/pe320qxt" data-wpel-link="external" target="_blank" rel="external noopener">Acer ProCreator</a><sup data-fn="4749f37b-43ba-438b-8cee-a467a1ec8c54" class="fn"><a href="#4749f37b-43ba-438b-8cee-a467a1ec8c54" id="4749f37b-43ba-438b-8cee-a467a1ec8c54-link">3</a></sup><a href="https://store.acer.com/en-sg/pe320qxt" data-wpel-link="external" target="_blank" rel="external noopener"> 31.5&#8243; 6K (PE320QXT)</a><sup data-fn="9b678dd3-6d30-4cb2-9ae5-01dd44f05da4" class="fn"><a href="#9b678dd3-6d30-4cb2-9ae5-01dd44f05da4" id="9b678dd3-6d30-4cb2-9ae5-01dd44f05da4-link">4</a></sup></th><th class="has-text-align-center" data-align="center"><a href="https://kuycon.us/monitors/G32P" data-wpel-link="external" target="_blank" rel="external noopener">Kuycon G32P</a></th><th class="has-text-align-center" data-align="center"><a href="https://www.meirro.com" data-wpel-link="external" target="_blank" rel="external noopener">Meirro Pro</a></th><th class="has-text-align-center" data-align="center"><a href="https://alogic.co/products/clarity-touch-6k-32-touchscreen-monitor" data-wpel-link="external" target="_blank" rel="external noopener">ALOGIC Clarity 32&#8243; 6K Multi-Touch (32C6KPDTF</a>)</th></tr></thead><tbody><tr><td>Screen diagonal</td><td class="has-text-align-center" data-align="center">81 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">80 cm</td><td class="has-text-align-center" data-align="center">81 cm</td></tr><tr><td>Resolution</td><td class="has-text-align-center" data-align="center">6,016 ⨉ 3,384</td><td class="has-text-align-center" data-align="center"><strong>6,144 ⨉ 3,456</strong></td><td class="has-text-align-center" data-align="center"><strong>6,144 ⨉ 3,456</strong></td><td class="has-text-align-center" data-align="center">6,016 ⨉ 3,384</td><td class="has-text-align-center" data-align="center">6,016 ⨉ 3,384</td><td class="has-text-align-center" data-align="center"><strong>6,144 ⨉ 3,456</strong></td><td class="has-text-align-center" data-align="center"><strong>6,144 ⨉ 3,456</strong></td><td class="has-text-align-center" data-align="center">6,016 ⨉ 3,384</td></tr><tr><td>Pixel count</td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center"><strong>21,233,664</strong></td><td class="has-text-align-center" data-align="center"><strong>21,233,664</strong></td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center"><strong>21,233,664</strong></td><td class="has-text-align-center" data-align="center"><strong>21,233,664</strong></td><td class="has-text-align-center" data-align="center">20,358,144</td></tr><tr><td>Backlight zones</td><td class="has-text-align-center" data-align="center"><strong>576</strong></td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td><td class="has-text-align-center" data-align="center">1</td></tr><tr><td>Pixels per backlight zone</td><td class="has-text-align-center" data-align="center"><strong>35,344</strong></td><td class="has-text-align-center" data-align="center">21,233,664</td><td class="has-text-align-center" data-align="center">21,233,664</td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center">20,358,144</td><td class="has-text-align-center" data-align="center">21,233,664</td><td class="has-text-align-center" data-align="center">21,233,664</td><td class="has-text-align-center" data-align="center">20,358,144</td></tr><tr><td>Pixel density</td><td class="has-text-align-center" data-align="center">218</td><td class="has-text-align-center" data-align="center">224</td><td class="has-text-align-center" data-align="center">224</td><td class="has-text-align-center" data-align="center">218</td><td class="has-text-align-center" data-align="center">218</td><td class="has-text-align-center" data-align="center">224</td><td class="has-text-align-center" data-align="center">224</td><td class="has-text-align-center" data-align="center">216</td></tr><tr><td>Contrast ratio</td><td class="has-text-align-center" data-align="center"><strong>1,000,000 : 1</strong></td><td class="has-text-align-center" data-align="center">2,000 : 1 <sup data-fn="27847762-88f0-44ea-ba59-70adf9684471" class="fn"><a href="#27847762-88f0-44ea-ba59-70adf9684471" id="27847762-88f0-44ea-ba59-70adf9684471-link">5</a></sup></td><td class="has-text-align-center" data-align="center">2,000 : 1 <sup data-fn="6a31577c-b563-4fb9-9aca-74b093f68a7b" class="fn"><a href="#6a31577c-b563-4fb9-9aca-74b093f68a7b" id="6a31577c-b563-4fb9-9aca-74b093f68a7b-link">6</a></sup></td><td class="has-text-align-center" data-align="center">1,500 : 1 <sup data-fn="a2835b86-4009-45a0-954b-86fce0627424" class="fn"><a href="#a2835b86-4009-45a0-954b-86fce0627424" id="a2835b86-4009-45a0-954b-86fce0627424-link">7</a></sup></td><td class="has-text-align-center" data-align="center">? <sup data-fn="66272084-6ab0-4db5-b4db-e2cf729a876b" class="fn"><a href="#66272084-6ab0-4db5-b4db-e2cf729a876b" id="66272084-6ab0-4db5-b4db-e2cf729a876b-link">8</a></sup></td><td class="has-text-align-center" data-align="center">2,000 : 1 <sup data-fn="ad6a8134-509e-40da-917a-9d2f267d4a44" class="fn"><a href="#ad6a8134-509e-40da-917a-9d2f267d4a44" id="ad6a8134-509e-40da-917a-9d2f267d4a44-link">9</a></sup></td><td class="has-text-align-center" data-align="center">2,000 : 1</td><td class="has-text-align-center" data-align="center">2,000 : 1</td></tr><tr><td>Peak sustained brightness</td><td class="has-text-align-center" data-align="center"><strong>1,600 (≤ 25℃)</strong></td><td class="has-text-align-center" data-align="center">450<sup data-fn="7cc0e42f-875c-4148-8ce7-17bbf1b8e484" class="fn"><a href="#7cc0e42f-875c-4148-8ce7-17bbf1b8e484" id="7cc0e42f-875c-4148-8ce7-17bbf1b8e484-link">10</a></sup></td><td class="has-text-align-center" data-align="center">450</td><td class="has-text-align-center" data-align="center">350<sup data-fn="f5a790c8-9793-46bf-800d-a9c1dbe707d2" class="fn"><a href="#f5a790c8-9793-46bf-800d-a9c1dbe707d2" id="f5a790c8-9793-46bf-800d-a9c1dbe707d2-link">11</a></sup></td><td class="has-text-align-center" data-align="center">400<sup data-fn="193920ba-441a-4e45-9080-768a741c6cc4" class="fn"><a href="#193920ba-441a-4e45-9080-768a741c6cc4" id="193920ba-441a-4e45-9080-768a741c6cc4-link">12</a></sup></td><td class="has-text-align-center" data-align="center">500</td><td class="has-text-align-center" data-align="center">450<sup data-fn="6dc243be-8225-4fa3-bf45-c01e13a73d72" class="fn"><a href="#6dc243be-8225-4fa3-bf45-c01e13a73d72" id="6dc243be-8225-4fa3-bf45-c01e13a73d72-link">13</a></sup></td><td class="has-text-align-center" data-align="center">400<sup data-fn="42dc9fd3-7bd6-4c06-b9db-d44aa6eb3aa3" class="fn"><a href="#42dc9fd3-7bd6-4c06-b9db-d44aa6eb3aa3" id="42dc9fd3-7bd6-4c06-b9db-d44aa6eb3aa3-link">14</a></sup></td></tr><tr><td>Maximum &#8220;black&#8221; luminence</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">≤ 0.1<sup data-fn="d231f696-9847-4c87-b843-921d7335e588" class="fn"><a href="#d231f696-9847-4c87-b843-921d7335e588" id="d231f696-9847-4c87-b843-921d7335e588-link">15</a></sup></td><td class="has-text-align-center" data-align="center">≤ 0.1<sup data-fn="ff1bcdce-6782-4a98-af23-0f60b16ebd43" class="fn"><a href="#ff1bcdce-6782-4a98-af23-0f60b16ebd43" id="ff1bcdce-6782-4a98-af23-0f60b16ebd43-link">16</a></sup></td><td class="has-text-align-center" data-align="center">≤ 0.1<sup data-fn="f3bcfdba-aa27-4817-8525-e3eab046df92" class="fn"><a href="#f3bcfdba-aa27-4817-8525-e3eab046df92" id="f3bcfdba-aa27-4817-8525-e3eab046df92-link">17</a></sup></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>Bit depth</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">8<sup data-fn="e4c5be92-9154-475e-bd28-f1103df3979a" class="fn"><a href="#e4c5be92-9154-475e-bd28-f1103df3979a" id="e4c5be92-9154-475e-bd28-f1103df3979a-link">18</a></sup></td><td class="has-text-align-center" data-align="center">8<sup data-fn="eb982973-5c34-43e3-9869-050092ea36d4" class="fn"><a href="#eb982973-5c34-43e3-9869-050092ea36d4" id="eb982973-5c34-43e3-9869-050092ea36d4-link">19</a></sup></td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">10</td><td class="has-text-align-center" data-align="center">8<sup data-fn="3f7be704-47f1-43e2-ad57-cd34c72c4906" class="fn"><a href="#3f7be704-47f1-43e2-ad57-cd34c72c4906" id="3f7be704-47f1-43e2-ad57-cd34c72c4906-link">20</a></sup></td></tr><tr><td>Rec 2020 coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcworld.com/article/3046133/lg-ultrafine-evo-32u990a-s-review.html" data-wpel-link="external" target="_blank" rel="external noopener">82%</a></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.tomshardware.com/monitors/asus-proart-pa32qcv-32-inch-6k-professional-monitor-review/4" data-wpel-link="external" target="_blank" rel="external noopener">73%</a></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>Display P3 coverage</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" target="_blank" rel="noreferrer noopener external" data-wpel-link="external">98.7%</a> <sup data-fn="1c5cb586-f38c-4cb7-b885-03d2d3b75f9b" class="fn"><a href="#1c5cb586-f38c-4cb7-b885-03d2d3b75f9b" id="1c5cb586-f38c-4cb7-b885-03d2d3b75f9b-link">21</a></sup></td><td class="has-text-align-center" data-align="center">98% <sup data-fn="567ea042-f79f-4461-9dfd-207c69270008" class="fn"><a href="#567ea042-f79f-4461-9dfd-207c69270008" id="567ea042-f79f-4461-9dfd-207c69270008-link">22</a></sup></td><td class="has-text-align-center" data-align="center"><strong>99%</strong></td><td class="has-text-align-center" data-align="center">98%</td><td class="has-text-align-center" data-align="center">99%</td><td class="has-text-align-center" data-align="center">99%</td><td class="has-text-align-center" data-align="center">98%</td><td class="has-text-align-center" data-align="center">99%<sup data-fn="faac7807-4660-467c-95d2-1174315e3eaf" class="fn"><a href="#faac7807-4660-467c-95d2-1174315e3eaf" id="faac7807-4660-467c-95d2-1174315e3eaf-link">23</a></sup></td></tr><tr><td>Adobe RGB coverage</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" target="_blank" rel="noreferrer noopener external" data-wpel-link="external">96.7%</a></td><td class="has-text-align-center" data-align="center"><strong>99.5%</strong></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.tomshardware.com/monitors/asus-proart-pa32qcv-32-inch-6k-professional-monitor-review/4" data-wpel-link="external" target="_blank" rel="external noopener">88%</a></td><td class="has-text-align-center" data-align="center">99%</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">99%</td><td class="has-text-align-center" data-align="center">99%<sup data-fn="a7b859c9-08ec-4019-9f0f-c9ede7cae87d" class="fn"><a href="#a7b859c9-08ec-4019-9f0f-c9ede7cae87d" id="a7b859c9-08ec-4019-9f0f-c9ede7cae87d-link">24</a></sup></td></tr><tr><td>Rec 709 coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><strong>100%</strong></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>sRGB coverage</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" target="_blank" rel="noreferrer noopener external" data-wpel-link="external">94.3%</a></td><td class="has-text-align-center" data-align="center"><a href="https://www.pcworld.com/article/3046133/lg-ultrafine-evo-32u990a-s-review.html" data-wpel-link="external" target="_blank" rel="external noopener">100%</a></td><td class="has-text-align-center" data-align="center"><strong>100%</strong></td><td class="has-text-align-center" data-align="center"><strong>100%</strong></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">99%</td><td class="has-text-align-center" data-align="center">99%</td><td class="has-text-align-center" data-align="center">100%</td></tr><tr><td>Refresh rate</td><td class="has-text-align-center" data-align="center">47.95 – 60.00 Hz</td><td class="has-text-align-center" data-align="center">30 &#8211; 60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz<sup data-fn="593b74a4-c31d-4592-bef5-717e89000a03" class="fn"><a href="#593b74a4-c31d-4592-bef5-717e89000a03" id="593b74a4-c31d-4592-bef5-717e89000a03-link">25</a></sup></td><td class="has-text-align-center" data-align="center">60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz</td><td class="has-text-align-center" data-align="center">60 Hz</td></tr><tr><td>Finish</td><td class="has-text-align-center" data-align="center">Glossy or Matte (&#8220;Nano-texture&#8221;)</td><td class="has-text-align-center" data-align="center">Matte</td><td class="has-text-align-center" data-align="center">Matte</td><td class="has-text-align-center" data-align="center">Matte</td><td class="has-text-align-center" data-align="center">Glossy</td><td class="has-text-align-center" data-align="center">Glossy</td><td class="has-text-align-center" data-align="center">Glossy</td><td class="has-text-align-center" data-align="center">Glossy</td></tr><tr><td>USB Power Delivery</td><td class="has-text-align-center" data-align="center">96W</td><td class="has-text-align-center" data-align="center">96W</td><td class="has-text-align-center" data-align="center"><strong>140W</strong></td><td class="has-text-align-center" data-align="center">96W</td><td class="has-text-align-center" data-align="center">90W</td><td class="has-text-align-center" data-align="center">100W</td><td class="has-text-align-center" data-align="center">96W</td><td class="has-text-align-center" data-align="center">90W<sup data-fn="4de8287a-c812-404e-8fce-294be1286b1a" class="fn"><a href="#4de8287a-c812-404e-8fce-294be1286b1a" id="4de8287a-c812-404e-8fce-294be1286b1a-link">26</a></sup></td></tr><tr><td>Connectivity</td><td class="has-text-align-center" data-align="center">1⨉ Thunderbolt 3<br>3⨉ USB-C (5 Gb/s<sup data-fn="4d2bc830-3dc1-4bc2-aed3-38a307275750" class="fn"><a href="#4d2bc830-3dc1-4bc2-aed3-38a307275750" id="4d2bc830-3dc1-4bc2-aed3-38a307275750-link">27</a></sup>)</td><td class="has-text-align-center" data-align="center"><strong>2⨉ Thunderbolt 5<br></strong>3⨉ USB-C (10 Gb/s, 1 up 2 down)<br>1⨉ DisplayPort 2.1<br>1⨉ HDMI 2.1</td><td class="has-text-align-center" data-align="center">2⨉ Thunderbolt 4<br><strong>5⨉ USB-C (10 Gb/s, 1 up 4 down)<br></strong>1⨉ Mini DisplayPort 2.?<br>1⨉ HDMI 2.?<br><strong>1⨉ 2.5 Gb Ethernet (RJ45)</strong></td><td class="has-text-align-center" data-align="center">2⨉ Thunderbolt 4<br>3⨉ USB-C (5 Gb/s, 1 up 2 down)<br>1x USB-C signal switch (for KVM)<br>1⨉ HDMI 2.1<br>1⨉ 3.5mm stereo audio (out)</td><td class="has-text-align-center" data-align="center">1⨉ USB-C (up)<br>?x USB-?<sup data-fn="2f2f73f2-377d-4982-bea6-d21c8d3ce64f" class="fn"><a href="#2f2f73f2-377d-4982-bea6-d21c8d3ce64f" id="2f2f73f2-377d-4982-bea6-d21c8d3ce64f-link">28</a></sup> (down)<br>2⨉ HDMI 2.1<br>1⨉ DisplayPort 1.4</td><td class="has-text-align-center" data-align="center">1⨉ USB-C (up)<br>2⨉ USB-C (down, 480 Mb/s)<br>1⨉ DisplayPort 2.1<br>2⨉ HDMI 2.1<br>1⨉ 3.5mm stereo audio (out)</td><td class="has-text-align-center" data-align="center">1⨉ USB-C (up, 80 Gb/s)<br>4⨉ USB-C (down, 2.0, 480 Mb/s)</td><td class="has-text-align-center" data-align="center">1⨉ USB-C 3 (up)<br>2⨉ USB-A 3 (down)<br>1⨉ DisplayPort 1.4<br>2⨉ HDMI 2.0<br>1⨉ 3.5mm stereo audio (out)</td></tr><tr><td> Built-in KVM</td><td class="has-text-align-center" data-align="center">No</td><td class="has-text-align-center" data-align="center">No<sup data-fn="eb03d0d5-20a2-4377-9a37-dc051226db01" class="fn"><a href="#eb03d0d5-20a2-4377-9a37-dc051226db01" id="eb03d0d5-20a2-4377-9a37-dc051226db01-link">29</a></sup></td><td class="has-text-align-center" data-align="center"><strong>Yes</strong></td><td class="has-text-align-center" data-align="center"><strong>Yes</strong></td><td class="has-text-align-center" data-align="center">No</td><td class="has-text-align-center" data-align="center">No</td><td class="has-text-align-center" data-align="center">No</td><td class="has-text-align-center" data-align="center">No</td></tr><tr><td>Dimensions (excluding stand)</td><td class="has-text-align-center" data-align="center">41.2 ⨉ 71.8 ⨉ 2.7 cm</td><td class="has-text-align-center" data-align="center"><strong>41 ⨉ 72 ⨉ 2.56 cm</strong></td><td class="has-text-align-center" data-align="center">49cm ⨉ 71cm ⨉ 6.6 cm</td><td class="has-text-align-center" data-align="center">41.97 ⨉ 71.42 ⨉ 4.69 cm</td><td class="has-text-align-center" data-align="center">? <sup data-fn="e38c8128-4bf3-4a23-bb34-ec0658dadd24" class="fn"><a href="#e38c8128-4bf3-4a23-bb34-ec0658dadd24" id="e38c8128-4bf3-4a23-bb34-ec0658dadd24-link">30</a></sup></td><td class="has-text-align-center" data-align="center">41.5 ⨉ 71.2 ⨉ 2.5 cm</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">42.9 ⨉ 72.5 ⨉ 5.7 cm</td></tr><tr><td>Naive volume<sup data-fn="a6444b2a-9fb8-4cd2-ae9c-59baab4a2450" class="fn"><a href="#a6444b2a-9fb8-4cd2-ae9c-59baab4a2450" id="a6444b2a-9fb8-4cd2-ae9c-59baab4a2450-link">31</a></sup> (excluding stand)</td><td class="has-text-align-center" data-align="center">7,987 cm³ </td><td class="has-text-align-center" data-align="center">7,559 cm³</td><td class="has-text-align-center" data-align="center">23,069 cm³</td><td class="has-text-align-center" data-align="center">14,058 cm³</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><strong>7,387 cm³</strong></td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">17,728 cm³</td></tr><tr><td>Weight w/ stand</td><td class="has-text-align-center" data-align="center">11.8 kg</td><td class="has-text-align-center" data-align="center">9.48 kg</td><td class="has-text-align-center" data-align="center">13.29 kg</td><td class="has-text-align-center" data-align="center"><strong>9.3 kg</strong></td><td class="has-text-align-center" data-align="center">10.05 kg</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>Weight w/o stand</td><td class="has-text-align-center" data-align="center">7.48 kg</td><td class="has-text-align-center" data-align="center"><strong>5.99 kg</strong></td><td class="has-text-align-center" data-align="center">8.62 kg</td><td class="has-text-align-center" data-align="center">6.3 kg</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center">7.5 kg</td><td class="has-text-align-center" data-align="center">8.92 kg</td><td class="has-text-align-center" data-align="center">?</td></tr><tr><td>Price<sup data-fn="e2dd17e9-482c-4022-9e7c-587556b9aff5" class="fn"><a href="#e2dd17e9-482c-4022-9e7c-587556b9aff5" id="e2dd17e9-482c-4022-9e7c-587556b9aff5-link">32</a></sup> w/o stand</td><td class="has-text-align-center" data-align="center">$4,999 US</td><td class="has-text-align-center" data-align="center">N/A</td><td class="has-text-align-center" data-align="center">N/A</td><td class="has-text-align-center" data-align="center">N/A</td><td class="has-text-align-center" data-align="center">N/A</td><td class="has-text-align-center" data-align="center">$1,699 US</td><td class="has-text-align-center" data-align="center"><strong>$1,149 US</strong></td><td class="has-text-align-center" data-align="center">N/A</td></tr><tr><td>Price<sup data-fn="29ffa28b-e98e-4783-9648-032d20c9ed69" class="fn"><a href="#29ffa28b-e98e-4783-9648-032d20c9ed69" id="29ffa28b-e98e-4783-9648-032d20c9ed69-link">33</a></sup> w/ stand</td><td class="has-text-align-center" data-align="center">$5,999 US</td><td class="has-text-align-center" data-align="center">$1,599 US</td><td class="has-text-align-center" data-align="center">$2,399 US</td><td class="has-text-align-center" data-align="center"><strong>$1,049 US</strong></td><td class="has-text-align-center" data-align="center">~$1,500 US <sup data-fn="1d8fe299-340e-4e7e-901e-883299643af4" class="fn"><a href="#1d8fe299-340e-4e7e-901e-883299643af4" id="1d8fe299-340e-4e7e-901e-883299643af4-link">34</a></sup></td><td class="has-text-align-center" data-align="center">$1,599 US<sup data-fn="c84194b4-f49d-4693-b251-d2ce0eb4ab13" class="fn"><a href="#c84194b4-f49d-4693-b251-d2ce0eb4ab13" id="c84194b4-f49d-4693-b251-d2ce0eb4ab13-link">35</a></sup></td><td class="has-text-align-center" data-align="center">$1,199 US</td><td class="has-text-align-center" data-align="center">$2,250 US</td></tr><tr><td>Introduced</td><td class="has-text-align-center" data-align="center">December 2019</td><td class="has-text-align-center" data-align="center">October 2025</td><td class="has-text-align-center" data-align="center">May 2023</td><td class="has-text-align-center" data-align="center">August 2025</td><td class="has-text-align-center" data-align="center">May 2025</td><td class="has-text-align-center" data-align="center">July 2025?</td><td class="has-text-align-center" data-align="center">August 2026</td><td class="has-text-align-center" data-align="center">October 2025</td></tr><tr><td>Discontinued</td><td class="has-text-align-center" data-align="center">March 2026</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td><td class="has-text-align-center" data-align="center">&#8211;</td></tr></tbody></table></figure>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">⚠️ I&#8217;d be careful with the Acer &#8211; there&#8217;s a lot of warning flags around it:</p>



<ul class="wp-block-list">
<li>While you <em>can</em> buy the Acer in the U.S., you can do so only from 3rd party retailers, and Acer&#8217;s U.S. website doesn&#8217;t seem to know the display exists.  That bodes ill for warranty &amp; repairs.</li>



<li>Acer&#8217;s marketing materials for the display are full of errors, and often self-contradictory.  They make misleading claims, like the largely fictitious 100,000,000 : 1 contrast ratio.</li>



<li>The official tech specs are missing key information, like the true contrast ratio, and even basic information like the display&#8217;s dimensions.</li>
</ul>



<p class="wp-block-paragraph">While Acer has been around for a long time, with a long presence in the western world, their behaviour here feels more like that of a Chinese brand.  I&#8217;d go with the Asus instead &#8211; if price is your main concern &#8211; or the LG (only slightly more expensive).</p>
</div></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">⚠️ Note on Kuycon: I added the G32P after <a href="https://kevinyank.com" data-wpel-link="external" target="_blank" rel="external noopener">Kevin Yank</a> suggested it. It&#8217;s a Chinese brand, which I would normally ignore for numerous reasons, <em>but</em> they actually have a functioning, well-designed English website, with a real working order system. So you can actually buy one in the western world. But be careful, nonetheless &#8211; there are plenty of anecdotes online about their non-existent customer support. Also, vs the LG &#8211; which uses the same panel &#8211; you lose Thunderbolt 5 &amp; KVM functionality.  But you <em>do</em> get a glossy finish, which is a big benefit.  And a design that shamelessly rips off the appearance of the Pro Display XDR (but without any of the actual benefits, like true HDR support and higher contrast ratio).<br><br>Also, there is a G32X which is similar but inferior &#8211; it has a lower contrast ratio and only 8-bit depth.</p>
</div></div>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">⚠️ Note on the Meirro Pro: it&#8217;s basically the Kuycon G32P slimmed down &#8211; very likely the same panel, probably the same glossy finish, just with even more-bare-bones connectivity and a slightly lower sustained brightness.  It doesn&#8217;t have the fans, either (though there are some versions of the Kuycon out there, from less reputable sources, that have the same flat-back metal finish without fans).  Meirro is just a brand under ClickClack.  I&#8217;m not sure about ClickClack &#8211; they&#8217;re definitely boutique at best, but they do seem to have been around for a while and a cursory web search doesn&#8217;t reveal any outrage or claims of foul play.  But ClickClack does bill themselves as a &#8216;group purchase collective&#8217;, with perhaps fickle interests, so while their products might be legitimate, I would assume no real warranty support.</p>
</div></div>


<ol class="wp-block-footnotes"><li id="a041ec8b-df15-4e40-85ce-8259991341aa">Apple don&#8217;t appear to have ever published a brightness spec for the original Retina iMac, but <a href="https://www.tomsguide.com/us/apple-imac-5k-retina-2015,review-3115.html" data-wpel-link="external" target="_blank" rel="external noopener">Tom&#8217;s Guide measured their review model at 382 lumens</a>.<br><br>The comparison to the original Retina iMac is apt because it was the <em>first</em> of the retina [desktop] displays, of which these 6k displays are all members.  It also had the exact same pixel density &#8211; 218 PPI &#8211; as the Apple and Asus displays &#8211; i.e. each individual pixel is the exact same size &#8211; so it&#8217;s a very fair point of comparison despite the overall differences in resolution. <a href="#a041ec8b-df15-4e40-85ce-8259991341aa-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="08a8dafe-81b7-4c14-94ec-907400f178dc">Speculation is that the Asus uses an AUO panel (the same one that&#8217;s popular with Chinese display manufacturers, because it&#8217;s very cheap).  I wouldn&#8217;t normally give this much weight, except it is interesting that both the Asus and the Chinese displays are frequently reported as making an annoying whining noise at certain brightness levels, and have <em>identical</em> panel specifications, and were released at similar times (notably, many years after the Pro Display XDR). <a href="#08a8dafe-81b7-4c14-94ec-907400f178dc-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="4749f37b-43ba-438b-8cee-a467a1ec8c54">Sometimes listed as &#8220;ProDesigner&#8221;, not &#8220;ProCreator&#8221; &#8211; I suspect the former is a U.S. variant of the name, as seen at retailers like <a href="https://www.bhphotovideo.com/c/product/1964373-REG/acer_um_jp0aa_003_prodesigner_pe320qxt_31_5_6k.html" data-wpel-link="external" target="_blank" rel="external noopener">B&amp;H</a>.  Perhaps someone told them that &#8220;ProCreator&#8221; might elicit snickers in some parts of the world (not that anyone told <a href="https://procreate.com" data-wpel-link="external" target="_blank" rel="external noopener">Procreate</a>, apparently). <a href="#4749f37b-43ba-438b-8cee-a467a1ec8c54-link" aria-label="Jump to footnote reference 3">↩︎</a></li><li id="9b678dd3-6d30-4cb2-9ae5-01dd44f05da4">Be careful not to confuse this with the <em>very</em> similarly named PE320QKX, which is a 4k OLED version.  Acer&#8217;s own marketing and website people confuse this (the product page erroneously claims that the PE320QXT has an OLED panel &#8211; it does <em>not</em>). <a href="#9b678dd3-6d30-4cb2-9ae5-01dd44f05da4-link" aria-label="Jump to footnote reference 4">↩︎</a></li><li id="27847762-88f0-44ea-ba59-70adf9684471">This is what LG states in the display&#8217;s specifications.  Yet, LG also states that this display is DisplayHDR 600 certified, which means <a href="https://displayhdr.org/performance-criteria/" data-wpel-link="external" target="_blank" rel="external noopener">it&#8217;s <em>required</em></a> to have a static contrast ratio of at least 8,000 : 1.  But, this requirement was added in v1.2 of the DisplayHDR standard, so perhaps LG are referring to the <em>old</em>, obsolete version of the standard. <a href="#27847762-88f0-44ea-ba59-70adf9684471-link" aria-label="Jump to footnote reference 5">↩︎</a></li><li id="6a31577c-b563-4fb9-9aca-74b093f68a7b">As with the LG, Dell <em>states</em> a 2,000 : 1 contrast ratio even though DisplayHDR 600 conformance requires at least 8,000 : 1. <a href="#6a31577c-b563-4fb9-9aca-74b093f68a7b-link" aria-label="Jump to footnote reference 6">↩︎</a></li><li id="a2835b86-4009-45a0-954b-86fce0627424">Asus <em>states</em> a 1,500 : 1 contrast ratio even though DisplayHDR 600 conformance requires at least 8,000 : 1.  Though they also describe the 1,500 : 1 as &#8220;typical&#8221; while also listing 3,000 : 1 as the maximum (but it appears the latter is only with &#8220;Dynamic Dimming&#8221; on, which is where it adjusts the overall backlight brightness based on average image brightness, but it does it too slowly such that you clearly see the transitions as the image changes, so <a href="https://wadetregaskis.com/asus-proart-pa32qcv/#Configuration_recommendations" data-wpel-link="internal">I recommend leaving it off</a> &#8211; plus, I can barely see any difference with it on anyway). <a href="#a2835b86-4009-45a0-954b-86fce0627424-link" aria-label="Jump to footnote reference 7">↩︎</a></li><li id="66272084-6ab0-4db5-b4db-e2cf729a876b">Acer list 100,000,000 : 1 with the caveat of &#8220;ACM&#8221; (Adaptive Contrast Management), a technique to <em>supposedly</em> achieve &#8216;visually equivalent&#8217; results to an intrinsically higher contrast ratio.  I&#8217;m not personally an expert on this, but the internet seems full of scepticism and critique of this technique as a whole, and contrast ratio claims based on it.  Unfortunately Acer don&#8217;t list the <em>actual</em> (&#8220;non-ACM&#8221;) contrast ratio. <a href="#66272084-6ab0-4db5-b4db-e2cf729a876b-link" aria-label="Jump to footnote reference 8">↩︎</a></li><li id="ad6a8134-509e-40da-917a-9d2f267d4a44">In a single breath Kuycon&#8217;s product page says <em>both</em> 1,500 : 1 and 2,000 : 1.  But the tech specs repeat the 2,000 : 1 number &#8211; and I suspect it&#8217;s using the LG panel which is listed as 2,000 : 1 in other displays &#8211; so I&#8217;m assuming the 1,500 : 1 is a typo. <a href="#ad6a8134-509e-40da-917a-9d2f267d4a44-link" aria-label="Jump to footnote reference 9">↩︎</a></li><li id="7cc0e42f-875c-4148-8ce7-17bbf1b8e484">LG says &#8220;typical&#8221; brightness is 450, with minimum being 360, without explaining the difference &#8211; e.g. whether that&#8217;s for a white patch vs full-screen white, or perhaps depending on ambient temperature.  I&#8217;m choosing to be generous and <em>assume</em> it&#8217;s merely a temperature thing, and not likely to be a concern in a typical indoor environment, because 360 is ridiculously dim.<br><br>Note that they claim to have VESA DisplayHDR 600 certification, which <em>requires</em> at least 350 lumens sustained but also at least 600 for a very small (8% of screen area) bright patch.  But also that Dell very likely use the exact same panel and they claim 450 lumens sustained.  So I&#8217;m not sure what the 360 is about… I&#8217;m <em>hoping</em> it&#8217;s just an overly cautious product lawyer that&#8217;s recognising that thermal throttling can happen at unusually high ambient temperatures. 🤞<br><br>Also, <a href="https://www.pcworld.com/article/3046133/lg-ultrafine-evo-32u990a-s-review.html" data-wpel-link="external" target="_blank" rel="external noopener">PCWorld measured the SDR brightness at 480</a>, which fits best with the 450 claim. <a href="#7cc0e42f-875c-4148-8ce7-17bbf1b8e484-link" aria-label="Jump to footnote reference 10">↩︎</a></li><li id="f5a790c8-9793-46bf-800d-a9c1dbe707d2">Sigh… brightness claims.  These days they&#8217;re subject to so many caveats and conditions that they&#8217;re verging on meaningless.<br><br>A luminance rating is conspicuously absent from <a href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/" data-wpel-link="external" target="_blank" rel="external noopener">the Asus product page</a> (other than the claim about DisplayHDR 600 certification, which merely sets a 250 nits minimum).  <a href="https://www.asus.com/us/displays-desktops/monitors/proart/proart-display-6k-pa32qcv/techspec/" data-wpel-link="external" target="_blank" rel="external noopener">Their tech specs</a> say 400 nits, though.<br><br><a href="https://www.pcworld.com/article/2873124/asus-proart-pa32qcv-review.html" data-wpel-link="external" target="_blank" rel="external noopener">PCWorld measured <em>peak</em> brightness at 714</a>, and <a href="https://www.tomshardware.com/monitors/asus-proart-pa32qcv-32-inch-6k-professional-monitor-review#:~:text=I%20measured%20almost%20650%20nits%20in%20my%20tests%2C%20and%20that%20was%20from%20both%20full%20field%20and%20window%20patterns" data-wpel-link="external" target="_blank" rel="external noopener">Tom&#8217;s Hardware measured it at ~650</a>.  Not the same as <em>sustained</em> brightness or full-screen brightness, though (neither of those reviewers described their methodology).<br><br>But, I&#8217;m not sure how they got those numbers &#8211; because <a href="https://wadetregaskis.com/asus-proart-pa32qcv/" data-type="post" data-id="8959" data-wpel-link="internal">I actually bought this display,</a> and I&#8217;ve seen nothing that indicates to me that it&#8217;s getting much more than 500 (though I don&#8217;t have a luminance measurement device &#8211; I&#8217;m comparing it to other displays like the LG UltraFine 5K and Apple M2 MacBook Air &#8211; both rated at 500 nits).<br><br>But, the actual <em><a href="https://www.bhphotovideo.com/lit_files/1239784.pdf" data-wpel-link="external" target="_blank" rel="external noopener">manual</a></em> says 350.  And I&#8217;m inclined to believe the manual, because it&#8217;s also the only place which actually admits that it&#8217;s <em>not</em> a 10-bit panel. <a href="#f5a790c8-9793-46bf-800d-a9c1dbe707d2-link" aria-label="Jump to footnote reference 11">↩︎</a></li><li id="193920ba-441a-4e45-9080-768a741c6cc4">Their terminology is non-standard, but they say that 400 is &#8220;native&#8221; while 600 is &#8220;peak&#8221;, which I take to mean 400 is the <em>sustained</em> maximum brightness. <a href="#193920ba-441a-4e45-9080-768a741c6cc4-link" aria-label="Jump to footnote reference 12">↩︎</a></li><li id="6dc243be-8225-4fa3-bf45-c01e13a73d72">The tech specs state &#8220;450 nits typical (SDR) • 750 nits (HDR enabled)&#8221;, which is a bit ambiguous.  But it seems very likely that this is the same panel used in the LG, so likely we can follow LG&#8217;s guidance there, which is a more explicit 450 nits <em>sustained</em> brightness (in typical temperature environments).  Meirro are the first to put a number to the peak brightness, however, at 750.  It&#8217;s possible they&#8217;ve tweaked the operating parameters in order to achieve that, but FWIW the [presumed] same panel in the LG was <a href="https://www.wired.com/review/lg-ultrafine-evo-6k-32-inch-monitor/#:~:text=I%20was%20able%20to%20measure%20640%20nits%20of%20peak%20brightness%20in%20HDR" data-wpel-link="external" target="_blank" rel="external noopener">reported by Wired as only 640 nits at peak</a>, but <a href="https://www.pcworld.com/article/3046133/lg-ultrafine-evo-32u990a-s-review.html#:~:text=the%20monitor%20reached%20a%20maximum%20HDR%20brightness%20of%20748%20nits" data-wpel-link="external" target="_blank" rel="external noopener">748 nits by PCWorld</a>, so while 750 seems plausible, it&#8217;s unclear if there&#8217;ll actually be a difference between the two.  Note that both of those measurements were &#8220;maximum&#8221;s, not necessarily something the monitor can sustain indefinitely. <a href="#6dc243be-8225-4fa3-bf45-c01e13a73d72-link" aria-label="Jump to footnote reference 13">↩︎</a></li><li id="42dc9fd3-7bd6-4c06-b9db-d44aa6eb3aa3">ALOGIC says &#8220;typical&#8221; brightness is 400, with minimum being 350, without explaining the difference &#8211; e.g. whether that&#8217;s for a white patch vs full-screen white, or perhaps depending on ambient temperature.  I&#8217;m choosing to be generous and <em>assume</em> it&#8217;s merely a temperature thing, and not likely to be a concern in a typical indoor environment, because 350 is ridiculously dim. <a href="#42dc9fd3-7bd6-4c06-b9db-d44aa6eb3aa3-link" aria-label="Jump to footnote reference 14">↩︎</a></li><li id="d231f696-9847-4c87-b843-921d7335e588">LG don&#8217;t explicitly state this, but it&#8217;s a requirement of the DisplayHDR 600 conformance. <a href="#d231f696-9847-4c87-b843-921d7335e588-link" aria-label="Jump to footnote reference 15">↩︎</a></li><li id="ff1bcdce-6782-4a98-af23-0f60b16ebd43">Dell don&#8217;t explicitly state this, but it&#8217;s a requirement of the DisplayHDR 600 conformance. <a href="#ff1bcdce-6782-4a98-af23-0f60b16ebd43-link" aria-label="Jump to footnote reference 16">↩︎</a></li><li id="f3bcfdba-aa27-4817-8525-e3eab046df92">Asus don&#8217;t explicitly state this, but it&#8217;s a requirement of the DisplayHDR 600 conformance. <a href="#f3bcfdba-aa27-4817-8525-e3eab046df92-link" aria-label="Jump to footnote reference 17">↩︎</a></li><li id="e4c5be92-9154-475e-bd28-f1103df3979a">It offers pseudo-10-bit mode using FRC (temporal dithering), but is not a true 10-bit display. <a href="#e4c5be92-9154-475e-bd28-f1103df3979a-link" aria-label="Jump to footnote reference 18">↩︎</a></li><li id="eb982973-5c34-43e3-9869-050092ea36d4">It offers pseudo-10-bit mode using FRC (temporal dithering), but is not a true 10-bit display. <a href="#eb982973-5c34-43e3-9869-050092ea36d4-link" aria-label="Jump to footnote reference 19">↩︎</a></li><li id="3f7be704-47f1-43e2-ad57-cd34c72c4906">It offers pseudo-10-bit mode using FRC (temporal dithering), but is not a true 10-bit display. <a href="#3f7be704-47f1-43e2-ad57-cd34c72c4906-link" aria-label="Jump to footnote reference 20">↩︎</a></li><li id="1c5cb586-f38c-4cb7-b885-03d2d3b75f9b">Apple don&#8217;t state the actual coverage &#8211; just vaguely reference the various colour gamut standards &#8211; so these are the figures as actually tested by PCMag. <a href="#1c5cb586-f38c-4cb7-b885-03d2d3b75f9b-link" aria-label="Jump to footnote reference 21">↩︎</a></li><li id="567ea042-f79f-4461-9dfd-207c69270008"><a href="https://www.pcworld.com/article/3046133/lg-ultrafine-evo-32u990a-s-review.html" data-wpel-link="external" target="_blank" rel="external noopener">96% in PCWorld&#8217;s testing</a>. <a href="#567ea042-f79f-4461-9dfd-207c69270008-link" aria-label="Jump to footnote reference 22">↩︎</a></li><li id="faac7807-4660-467c-95d2-1174315e3eaf">ALOGIC&#8217;s flashier marketing material claims 100%, but the tech specs say 99% &#8211; and some older marketing material on Amazon says 97%.  I&#8217;m sceptical of the 100% coverage claim &#8211; and of marketing people in general &#8211; and not sure what to make of the 97% number from 3rd parties, so I&#8217;m going with the actual tech specs. <a href="#faac7807-4660-467c-95d2-1174315e3eaf-link" aria-label="Jump to footnote reference 23">↩︎</a></li><li id="a7b859c9-08ec-4019-9f0f-c9ede7cae87d">ALOGIC&#8217;s flashier marketing material claims 100%, but the tech specs say 99%.  I&#8217;m sceptical of the 100% coverage claim &#8211; and of marketing people in general &#8211; so I&#8217;m going with the actual tech specs. <a href="#a7b859c9-08ec-4019-9f0f-c9ede7cae87d-link" aria-label="Jump to footnote reference 24">↩︎</a></li><li id="593b74a4-c31d-4592-bef5-717e89000a03">Acer&#8217;s product page claims 240 Hz, but that seems to clearly be an error &#8211; it&#8217;s referring to <em>other</em> ProCreator models, in much smaller sizes and lower resolutions. <a href="#593b74a4-c31d-4592-bef5-717e89000a03-link" aria-label="Jump to footnote reference 25">↩︎</a></li><li id="4de8287a-c812-404e-8fce-294be1286b1a">ALOGIC sometimes says 90W (e.g. in the dominant marketing material) but the tech specs say 95W.  I&#8217;m erring on the side of caution here, by going with the lower number.  Though in practice I don&#8217;t think this 5W difference is significant in any case. <a href="#4de8287a-c812-404e-8fce-294be1286b1a-link" aria-label="Jump to footnote reference 26">↩︎</a></li><li id="4d2bc830-3dc1-4bc2-aed3-38a307275750">Only when used with Macs which support DSC (Display Stream Compression), otherwise the USB-C ports are limited to USB 2.0 (400 Mb/s). <a href="#4d2bc830-3dc1-4bc2-aed3-38a307275750-link" aria-label="Jump to footnote reference 27">↩︎</a></li><li id="2f2f73f2-377d-4982-bea6-d21c8d3ce64f">Acer&#8217;s marketing pages, product specs, and release announcements list conflicting and non-sensical things (e.g. &#8220;USB-B&#8221;), so while it seems clear it has some downstream USB ports, it&#8217;s not at all clear how many, what port type, or what speeds. <a href="#2f2f73f2-377d-4982-bea6-d21c8d3ce64f-link" aria-label="Jump to footnote reference 28">↩︎</a></li><li id="eb03d0d5-20a2-4377-9a37-dc051226db01">The tech specs claim it has a built-in KVM, but there&#8217;s no mention of that anywhere else in the marketing materials nor the user manual.  It does have a USB hub, which you can <em>manually</em> switch between the Thunderbolt or USB upstream ports by diving into the on-screen display, but there&#8217;s no apparent support for (a) doing this automatically when the input source changes nor (b) switching input sources &amp; USB routing via keypress. <a href="#eb03d0d5-20a2-4377-9a37-dc051226db01-link" aria-label="Jump to footnote reference 29">↩︎</a></li><li id="e38c8128-4bf3-4a23-bb34-ec0658dadd24">Acer&#8217;s product page specifies the dimensions as &#8220;72.55 × 52.77~28.28 × 32.11 cm (28.56 × 20.77~11.13 × 12.64 in)&#8221;.  I have no idea what that&#8217;s supposed to mean, but it&#8217;s clearly not correct no matter how you interpret it (the display is assuredly not a foot deep). <a href="#e38c8128-4bf3-4a23-bb34-ec0658dadd24-link" aria-label="Jump to footnote reference 30">↩︎</a></li><li id="a6444b2a-9fb8-4cd2-ae9c-59baab4a2450">Meaning the simple product of the three maximal dimensions.  Some of these displays have curved backs, so their actual volume will be substantially less. <a href="#a6444b2a-9fb8-4cd2-ae9c-59baab4a2450-link" aria-label="Jump to footnote reference 31">↩︎</a></li><li id="e2dd17e9-482c-4022-9e7c-587556b9aff5">Note that MRRP (Manufacturer Recommended Retail Price) may vary over time, not to mention sales or other store-specific price changes.  At various times I&#8217;ve found most of these displays on sale for at least a few hundred dollars less than their list price. <a href="#e2dd17e9-482c-4022-9e7c-587556b9aff5-link" aria-label="Jump to footnote reference 32">↩︎</a></li><li id="29ffa28b-e98e-4783-9648-032d20c9ed69">Note that MRRP (Manufacturer Recommended Retail Price) may vary over time, not to mention sales or other store-specific price changes.  At various times I&#8217;ve found most of these displays on sale for at least a few hundred dollars less than their list price. <a href="#29ffa28b-e98e-4783-9648-032d20c9ed69-link" aria-label="Jump to footnote reference 33">↩︎</a></li><li id="1d8fe299-340e-4e7e-901e-883299643af4">Acer only lists this display model on their Singapore website, though it is available from some U.S. retailers (e.g. <a href="https://www.bhphotovideo.com/c/product/1964373-REG/acer_um_jp0aa_003_prodesigner_pe320qxt_31_5_6k.html" data-wpel-link="external" target="_blank" rel="external noopener">B&amp;H</a>).  At time of writing (30th of April 2026) the price in Singapore ($1,799 SGD) translates to ~$1,400 US, but I have not been able to find any U.S. retailer offering it for less than $1,500. <a href="#1d8fe299-340e-4e7e-901e-883299643af4-link" aria-label="Jump to footnote reference 34">↩︎</a></li><li id="c84194b4-f49d-4693-b251-d2ce0eb4ab13">At time of writing, only when purchased via <a href="https://clickclack.io/products/in-stock-kuycon-g32p-6k-32-inch-ips-black-monitor" data-wpel-link="external" target="_blank" rel="external noopener">ClickClack</a>, not Kuycon&#8217;s website direct.  And yes, it&#8217;s therefore cheaper with the stand than without (since ClickClack sell only the with-stand version). <a href="#c84194b4-f49d-4693-b251-d2ce0eb4ab13-link" aria-label="Jump to footnote reference 35">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/6k-display-comparison/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8747</post-id>	</item>
		<item>
		<title>Studio Display XDR vs Pro Display XDR</title>
		<link>https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/</link>
					<comments>https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 03 Mar 2026 17:58:28 +0000</pubDate>
				<category><![CDATA[News]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Pro Display XDR]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Studio Display XDR]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8738</guid>

					<description><![CDATA[Studio Display XDR Pro Display XDR Screen diagonal 69 cm 81 cm (+17%) Resolution 5,120 ⨉ 2,880 6,016 ⨉ 3,384 Pixel count 14,745,600 20,358,144 (+38%) Backlight zones 2,304 (+300%) 576 Pixel density 218 218 Contrast ratio 1,000,000 : 1 1,000,000 : 1 Peak sustained brightness 2,000 (≤25℃) (+25%) 1,600 (≤ 25℃) Display P3 coverage ?&#8230; <a class="read-more-link" href="https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-table aligncenter is-style-stripes"><table><thead><tr><th></th><th class="has-text-align-center" data-align="center">Studio Display XDR</th><th class="has-text-align-center" data-align="center">Pro Display XDR</th></tr></thead><tbody><tr><td>Screen diagonal</td><td class="has-text-align-center" data-align="center">69 cm</td><td class="has-text-align-center" data-align="center">81 cm (+17%)</td></tr><tr><td>Resolution</td><td class="has-text-align-center" data-align="center">5,120 ⨉ 2,880</td><td class="has-text-align-center" data-align="center"><strong>6,016 ⨉ 3,384</strong></td></tr><tr><td>Pixel count</td><td class="has-text-align-center" data-align="center">14,745,600</td><td class="has-text-align-center" data-align="center"><strong>20,358,144</strong> (+38%)</td></tr><tr><td>Backlight zones</td><td class="has-text-align-center" data-align="center"><strong>2,304</strong> (+300%)</td><td class="has-text-align-center" data-align="center">576</td></tr><tr><td>Pixel density</td><td class="has-text-align-center" data-align="center">218</td><td class="has-text-align-center" data-align="center">218</td></tr><tr><td>Contrast ratio</td><td class="has-text-align-center" data-align="center">1,000,000 : 1</td><td class="has-text-align-center" data-align="center">1,000,000 : 1</td></tr><tr><td>Peak sustained brightness</td><td class="has-text-align-center" data-align="center"><strong>2,000</strong> (≤25℃) (+25%)</td><td class="has-text-align-center" data-align="center">1,600 (≤ 25℃)</td></tr><tr><td>Display P3 coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" data-wpel-link="external" target="_blank" rel="external noopener">98.7%</a></td></tr><tr><td>Adobe RGB coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" data-wpel-link="external" target="_blank" rel="external noopener">96.7%</a></td></tr><tr><td>sRGB coverage</td><td class="has-text-align-center" data-align="center">?</td><td class="has-text-align-center" data-align="center"><a href="https://www.pcmag.com/reviews/apple-pro-display-xdr" data-wpel-link="external" target="_blank" rel="external noopener">94.3%</a></td></tr><tr><td>Refresh rate</td><td class="has-text-align-center" data-align="center"><strong>47 &#8211; 120 Hz</strong></td><td class="has-text-align-center" data-align="center">47.95 &#8211; 60.00 Hz</td></tr><tr><td>USB Power Delivery</td><td class="has-text-align-center" data-align="center"><strong>140W</strong> (+46%)</td><td class="has-text-align-center" data-align="center">96W</td></tr><tr><td>Connectivity</td><td class="has-text-align-center" data-align="center"><strong>Thunderbolt 5 (1 up, 1 down) + 2 USB-C (10 Gb/s)</strong></td><td class="has-text-align-center" data-align="center">Thunderbolt 3 (1 up) + 3 USB-C (5 Gb/s<sup data-fn="e8feb3fe-feb2-4a81-83f9-5562e245b289" class="fn"><a href="#e8feb3fe-feb2-4a81-83f9-5562e245b289" id="e8feb3fe-feb2-4a81-83f9-5562e245b289-link">1</a></sup>)</td></tr><tr><td>Dimensions (excluding stand)</td><td class="has-text-align-center" data-align="center">36.2 ⨉ 62.3 ⨉ 3.3 cm</td><td class="has-text-align-center" data-align="center">41.2 ⨉ 71.8 ⨉ 2.7 cm</td></tr><tr><td>Volume (excluding stand)</td><td class="has-text-align-center" data-align="center">7,442 cm³</td><td class="has-text-align-center" data-align="center">7,987 cm³ (+7%)</td></tr><tr><td>Weight w/ stand</td><td class="has-text-align-center" data-align="center">8.5 kg</td><td class="has-text-align-center" data-align="center">11.8 kg (+39%)</td></tr><tr><td>Weight w/o stand</td><td class="has-text-align-center" data-align="center">6.3 kg</td><td class="has-text-align-center" data-align="center">7.48 kg (+19%)</td></tr><tr><td>Price w/o stand</td><td class="has-text-align-center" data-align="center"><s>$3,299</s> $2,899 US</td><td class="has-text-align-center" data-align="center">$4,999 US (<s>+52%</s> +72%)</td></tr><tr><td>Price w/o stand w/ nano texture</td><td class="has-text-align-center" data-align="center"><s>$3,599</s> $3,199 US</td><td class="has-text-align-center" data-align="center">$5,999 US (<s>+67%</s> +88%)</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">All in all… meh.</p>



<p class="wp-block-paragraph">28% fewer pixels for <s>34%</s> 42% fewer dollars (47% if you&#8217;re talking nano-textured) &#8211; so technically better value, if you don&#8217;t really care about screen real-estate. But that extra real estate is really valuable, <em>and</em> Apple have now apparently ceded the large display market to… well, mostly the tumbleweeds. Sure, <a href="https://wadetregaskis.com/6k-display-comparison/" data-type="post" data-id="8747" data-wpel-link="internal">there&#8217;s <em>technically</em> other 6k displays</a>, like the <a href="https://www.lg.com/us/monitors/lg-32u990a-s-ultrafine-monitor" data-wpel-link="external" target="_blank" rel="external noopener">LG</a>, the <a href="https://www.dell.com/en-us/shop/dell-ultrasharp-32-6k-monitor-u3224kb/apd/210-bhbz/monitors-monitor-accessories" data-wpel-link="external" target="_blank" rel="external noopener">Dell</a>, or the <a href="https://shop.asus.com/us/90lm0bd0-b01kb2-proart-display-6k-pa32qcv.html" data-wpel-link="external" target="_blank" rel="external noopener">Asus</a>, but while they have some advantages &#8211; less than half the price, most notably &#8211; they have real big disadvantages &#8211; like low brightness and poor contrast ratios.</p>



<p class="wp-block-paragraph">4⨉ the backlight zones is a significant improvement, I&#8217;ll grant Apple that.  But it doesn&#8217;t eliminate the blooming that was problematic with the Pro Display XDR, merely reduces it.  In an era of OLED displays &#8211; hell, my old LG TV from nearly a decade ago has an OLED display; this ain&#8217;t new technology &#8211; a brand new, ostensibly-high-end &#8220;studio&#8221; display still running on LED backlighting is just sad.</p>



<p class="wp-block-paragraph">The extra brightness of the Studio Display XDR is merely nice &#8211; an extra 25% isn&#8217;t a big difference (certainly nothing like the +200% or so in going from a competing 6k display to the Apple Pro Display XDR).  Props to Apple for the improvement, but it&#8217;s minor.</p>



<p class="wp-block-paragraph">It&#8217;s interesting that the new, smaller, lower-resolution Studio Display XDR is nearly the same spatial volume as its big sister (and not as much lighter as its reduced resolution and screen dimensions would suggest).  I wonder if that&#8217;s dictated by thermals?</p>



<p class="wp-block-paragraph">I didn&#8217;t bother including the audio &amp; camera aspects because I&#8217;m genuinely confused as to who, in the market for an expensive display, would care?  If you&#8217;re doing photography there&#8217;s no sound anyway, and if you&#8217;re doing videography in this price range you should be using real speakers or headphones.</p>



<p class="wp-block-paragraph">And the camera… sigh… I miss the <a href="https://paulstamatiou.com/gear/apple-isight" data-wpel-link="external" target="_blank" rel="external noopener">iSight</a>, which gave you a much better camera &#8211; thanks to physics &#8211; that you could <em>optionally</em> buy and use.  And look at it &#8211; it was a beautiful design that functioned <em>really well</em>, that would have fit in perfectly with the Pro Display XDR!</p>



<p class="wp-block-paragraph">I&#8217;m also choosing to overlook the firmware, which I assume uses the same weird, bastardised, glitchy version of iOS as the prior Studio Display model.</p>



<p class="wp-block-paragraph">After <em>more than six years</em>, I was hoping for an improved Pro Display XDR, not merely a small version.</p>


<ol class="wp-block-footnotes"><li id="e8feb3fe-feb2-4a81-83f9-5562e245b289">Only when used with Macs which support DSC (Display Stream Compression), otherwise the USB-C ports are limited to USB 2.0 (400 Mb/s). <a href="#e8feb3fe-feb2-4a81-83f9-5562e245b289-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/studio-display-xdr-vs-pro-display-xdr/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8738</post-id>	</item>
		<item>
		<title>Bugs Apple Loves &#038; Apps Apple Hates</title>
		<link>https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/</link>
					<comments>https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Mon, 26 Jan 2026 01:48:06 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Snafu]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8717</guid>

					<description><![CDATA[It&#8217;s been a while since I&#8217;ve seen such a pithy and accurate representation of what it&#8217;s like being an Apple Mac &#38; iPhone user these days (well done Nick Hodulik!). The externalities cost estimates might be a little tongue-in-cheek, but honestly, are they all that wrong? One small irritation at the wrong moment can ricochet&#8230; <a class="read-more-link" href="https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">It&#8217;s been a while since I&#8217;ve seen such <a href="https://www.bugsappleloves.com" data-wpel-link="external" target="_blank" rel="external noopener">a pithy and accurate representation of what it&#8217;s like being an Apple Mac &amp; iPhone user these days</a> (well done <a href="https://github.com/nhod" data-wpel-link="external" target="_blank" rel="external noopener">Nick Hodulik</a>!).</p>



<p class="wp-block-paragraph">The externalities cost estimates might be a little tongue-in-cheek, but honestly, are they all that wrong?  One small irritation at the wrong moment can ricochet my happy mood off into the doldrums, and Apple&#8217;s products produce a hundred &#8220;small&#8221; irritations every day &#8211; which compound in their irritation when you see them software update after software update, year after year, product after product.  It&#8217;s hard not to take it personally.  Like Apple is <em>deliberately</em> being cruel.</p>



<p class="wp-block-paragraph"><a href="https://ikennd.ac/about/" data-wpel-link="external" target="_blank" rel="external noopener">Daniel Kennett</a> wrote, in <a href="https://ikennd.ac/blog/2026/01/old-man-yells-at-modern-software-design/" data-wpel-link="external" target="_blank" rel="external noopener">his memorial to Aperture</a>, about how the Mac-using experience wasn&#8217;t <em>actually</em> rainbows and perfection even back in whatever you personally feel was the golden age (I&#8217;m with him that circa System 7.1 was glorious, though the early days of Mac OS X were also very special to me).  Which is true &#8211; fire up your favourite old Mac on <a href="https://infinitemac.org" data-wpel-link="external" target="_blank" rel="external noopener">Infinite Mac</a> and see the strength of your rose-tinted nostalgia glasses.</p>



<p class="wp-block-paragraph"><em>But</em>, the big difference is that Apple back then was a relatively tiny company struggling just to survive in a brutal industry dominated by <a href="https://www.ibm.com" data-wpel-link="external" target="_blank" rel="external noopener">soulless</a>, <a href="https://www.microsoft.com" data-wpel-link="external" target="_blank" rel="external noopener">greedy</a> <a href="https://www.intel.com" data-wpel-link="external" target="_blank" rel="external noopener">monsters</a>.</p>



<p class="wp-block-paragraph">There is a point at which mere indifference or incompetence transitions into negligence, and it&#8217;s <em>long</em> before you become one of the wealthiest companies on the planet with a veritable army of engineers.</p>



<p class="wp-block-paragraph">Having worked at Apple &#8211; among other big tech companies &#8211; I can say with confidence that there&#8217;s no valid reason why they cannot fix long-standing, <em>infamous</em> bugs.  It&#8217;s.  Not.  That.  Hard.  One half-decent engineer could fix everything listed on Bugs Apple Loves in six months, single-handed.</p>



<p class="wp-block-paragraph">It&#8217;s not apparent why that doesn&#8217;t happen, but it&#8217;s not that Apple are technically incapable of it, and it cannot be that they&#8217;re unaware, so it must be that they&#8217;re choosing not to.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/bugs-apple-loves-apps-apple-hates/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8717</post-id>	</item>
		<item>
		<title>We&#8217;ve been in squircle jail before</title>
		<link>https://wadetregaskis.com/weve-been-in-squircle-jail-before/</link>
					<comments>https://wadetregaskis.com/weve-been-in-squircle-jail-before/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 23 Jan 2026 16:41:36 +0000</pubDate>
				<category><![CDATA[Ancient History]]></category>
		<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[At Ease]]></category>
		<category><![CDATA[infinitemac.org]]></category>
		<category><![CDATA[Launchpad]]></category>
		<category><![CDATA[Mac OS 9]]></category>
		<category><![CDATA[macOS 26 Tahoe]]></category>
		<category><![CDATA[MacOS X 10.7 Lion]]></category>
		<category><![CDATA[squircle jail]]></category>
		<category><![CDATA[System 7.1]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8696</guid>

					<description><![CDATA[I&#8217;m not using macOS 26 Tahoe yet &#8211; hopefully I&#8217;ll never have to; 🤞 the next version of macOS is less of a dumpster fire, and I&#8217;ll just skip Tahoe entirely. But, looking over other people&#8217;s shoulders, and listening to the groans and cries, I&#8217;m struck by something. We&#8217;ve been here before. Icon jail, I&#8230; <a class="read-more-link" href="https://wadetregaskis.com/weve-been-in-squircle-jail-before/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I&#8217;m not using macOS 26 Tahoe yet &#8211; hopefully I&#8217;ll never have to; 🤞 the next version of macOS is less of a dumpster fire, and I&#8217;ll just skip Tahoe entirely.</p>



<p class="wp-block-paragraph">But, looking over other people&#8217;s shoulders, and listening to the groans and cries, I&#8217;m struck by something.  We&#8217;ve been here before.  Icon jail, I mean (&#8220;Squircle&#8221; jail specifically in Tahoe&#8217;s case):</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="701" height="346" src="https://wadetregaskis.com/wp-content/uploads/2026/01/macos-26-tahoe-squircle-jail.webp" alt="Screenshot from macOS 26 Tahoe showing various application icons, many of them stuck in squircle jail" class="wp-image-8697" srcset="https://wadetregaskis.com/wp-content/uploads/2026/01/macos-26-tahoe-squircle-jail.webp 701w, https://wadetregaskis.com/wp-content/uploads/2026/01/macos-26-tahoe-squircle-jail-256x126.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/01/macos-26-tahoe-squircle-jail@2x.webp 1402w, https://wadetregaskis.com/wp-content/uploads/2026/01/macos-26-tahoe-squircle-jail-256x126@2x.webp 512w" sizes="auto, (max-width: 701px) 100vw, 701px" /><figcaption class="wp-element-caption">Screenshot courtesy of <a href="https://iconfactory.world/@Iconfactory/114659727480043761" data-wpel-link="external" target="_blank" rel="external noopener">the Iconfactory</a>.</figcaption></figure>
</div>


<p class="wp-block-paragraph">This isn&#8217;t the first time Apple&#8217;s put icons in grey jail.  Mac OS 9 already did this:</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="640" height="480" src="https://wadetregaskis.com/wp-content/uploads/2026/01/mac-os-9-with-22view…-as-buttons22-enabled-in-the-finder-1.webp" alt="" class="wp-image-8702" srcset="https://wadetregaskis.com/wp-content/uploads/2026/01/mac-os-9-with-22view…-as-buttons22-enabled-in-the-finder-1.webp 640w, https://wadetregaskis.com/wp-content/uploads/2026/01/mac-os-9-with-22view…-as-buttons22-enabled-in-the-finder-1-256x192.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/01/mac-os-9-with-22view…-as-buttons22-enabled-in-the-finder-1-256x192@2x.webp 512w" sizes="auto, (max-width: 640px) 100vw, 640px" /><figcaption class="wp-element-caption">Screenshot taken in <a href="https://infinitemac.org" data-wpel-link="external" target="_blank" rel="external noopener">https://infinitemac.org</a>&#8216;s Mac OS 9 emulator</figcaption></figure>
</div>


<p class="wp-block-paragraph">This mode made the icons activate with a single click rather than a double-click.  It was a window-specific (and desktop-specific) setting.  I vaguely recall it being sold as a simpler, more efficient way of using the Finder &#8211; fewer clicks!  It did not catch on, though possibly there were some fans.  I vaguely recall encountering Macs in the wild that had this mode enabled.</p>



<p class="wp-block-paragraph">It wasn&#8217;t new in Mac OS 9 either, really &#8211; it was actually introduced with <a href="https://en.wikipedia.org/wiki/At_Ease" data-wpel-link="external" target="_blank" rel="external noopener">At Ease</a> circa System 7.1:</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="563" height="404" src="https://wadetregaskis.com/wp-content/uploads/2026/01/at-ease.webp" alt="" class="wp-image-8703" srcset="https://wadetregaskis.com/wp-content/uploads/2026/01/at-ease.webp 563w, https://wadetregaskis.com/wp-content/uploads/2026/01/at-ease-256x184.webp 256w, https://wadetregaskis.com/wp-content/uploads/2026/01/at-ease-256x184@2x.webp 512w" sizes="auto, (max-width: 563px) 100vw, 563px" /><figcaption class="wp-element-caption">Screenshot courtesy of <a href="https://porn.quiteajolt.com/about-me/" data-wpel-link="external" target="_blank" rel="external noopener">‘Ili Butterfield</a> via <a href="https://porn.quiteajolt.com/2009/03/16/old-mac-cds-2-the-macintosh-demo-applications-cd-version-11/" data-wpel-link="external" target="_blank" rel="external noopener">his website</a>.</figcaption></figure>
</div>


<p class="wp-block-paragraph">In a way, Apple is just returning to its roots; <em>continuing</em> to try to dumb-down the Mac.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">Incidentally, <a href="https://notebooks.com/2011/07/22/how-to-get-the-most-out-of-lions-launchpad/" data-wpel-link="external" target="_blank" rel="external noopener">Launchpad</a>, introduced in MacOS X 10.7 Lion in 2011, was also very similar to At Ease&#8217;s app launcher (by way of iOS, of course).  Ironically it&#8217;s <em>removed</em> in macOS 26 Tahoe (in favour of Spotlight, which would be fine <em>if</em> Spotlight ever worked reliably) although <a href="https://www.macobserver.com/news/launchpad-is-gone-in-macos-26-but-you-can-still-restore-it/" data-wpel-link="external" target="_blank" rel="external noopener">it is possible to resurrect it</a>.</p>
</div></div>



<p class="wp-block-paragraph">Of course, a big difference between all these prior incarnations and macOS 26 Tahoe&#8217;s is that previously the user controlled it, and could turn it off.  You didn&#8217;t have to install At Ease at all.  Mac OS 9 didn&#8217;t use the Buttons view mode by default, you had to enable it, and anyone could disable it again at any time.</p>



<p class="wp-block-paragraph">That&#8217;s where today&#8217;s Apple <em>actually</em> differs from yesteryear&#8217;s &#8211; they used to respect the user, and now they don&#8217;t.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/weve-been-in-squircle-jail-before/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/01/macos-26-tahoe-squircle-jail.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8696</post-id>	</item>
		<item>
		<title>Cross Dissolve only the video, not the audio, in Final Cut Pro</title>
		<link>https://wadetregaskis.com/cross-dissolve-only-the-video-not-the-audio-in-final-cut-pro/</link>
					<comments>https://wadetregaskis.com/cross-dissolve-only-the-video-not-the-audio-in-final-cut-pro/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Fri, 23 Jan 2026 02:51:10 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Cross Dissolve]]></category>
		<category><![CDATA[Final Cut Pro]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8689</guid>

					<description><![CDATA[When you drop a Cross Dissolve transition onto a clip in Final Cut Pro it applies the transition to both video and audio. That&#8217;s great &#8211; typically that&#8217;s what you want &#8211; but sometimes you don&#8217;t want that. Contrary to what you might read online or what the dumb robots might tell you (because they&#8230; <a class="read-more-link" href="https://wadetregaskis.com/cross-dissolve-only-the-video-not-the-audio-in-final-cut-pro/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">When you drop a Cross Dissolve transition onto a clip in Final Cut Pro it applies the transition to both video and audio.  That&#8217;s great &#8211; typically that&#8217;s what you want &#8211; but sometimes you <em>don&#8217;t</em> want that.</p>



<p class="wp-block-paragraph">Contrary to what you might <a href="https://www.reddit.com/r/finalcutpro/comments/q9fwlr/how_do_i_cross_dissolve_the_image_but_not_the/" data-wpel-link="external" target="_blank" rel="external noopener">read online</a> or what the dumb robots might tell you (because they just plagiarise it from those same Reddit threads and YouTube videos), you can&#8217;t simply &#8220;Expand Audio&#8221; and apply the Cross Dissolve to just the video lane<sup data-fn="fe74553d-2443-42af-bf51-d9dc058a400d" class="fn"><a href="#fe74553d-2443-42af-bf51-d9dc058a400d" id="fe74553d-2443-42af-bf51-d9dc058a400d-link">1</a></sup>.  You <em>can</em> detach the audio entirely, but that&#8217;s heavy-handed and may make your life much harder downstream (since Final Cut Pro will then treat the separated audio and video as completely independent clips).</p>



<p class="wp-block-paragraph">Actually, there&#8217;s a simple &#8216;hack&#8217; which works:</p>



<ol class="wp-block-list">
<li><strong>Drag the Cross Dissolve (or similar) onto the clip</strong>.</li>



<li><strong>Select the clip and &#8220;Expand Audio&#8221;</strong> (from the Clip menu, contextual pop-up menu, or by pressing ⌃S).  This is really just to get access to the audio track that&#8217;s otherwise hidden under the Cross Dissolve.</li>



<li><strong>Carefully drag the audio fade-in handle to the edge of the clip</strong>, to <em>exactly</em> where the tooltip says the offset is zero.  Don&#8217;t go too far &#8211; if you go over the end of the clip it&#8217;ll re-install the audio fade!</li>
</ol>



<p class="wp-block-paragraph">It <em>feels</em> like that shouldn&#8217;t work &#8211; like it&#8217;s actually a subtle bug where Final Cut Pro actually intends to ignore your zero-duration fade-in and reset it back to the full length, like it does if you drag just a pixel too far.  But hey, thank goodness for some bugs!</p>



<figure class="wp-block-video"><video height="248" style="aspect-ratio: 774 / 248;" width="774" autoplay loop muted preload="auto" src="https://wadetregaskis.com/wp-content/uploads/2026/01/How-to-apply-a-Cross-Dissolve-to-only-the-video-of-a-clip-not-the-audio-in-Final-Cut-Pro-AV1.mp4" playsinline></video></figure>


<ol class="wp-block-footnotes"><li id="fe74553d-2443-42af-bf51-d9dc058a400d">Maybe this did work in an earlier version of Final Cut Pro, but it definitely does not in 11.2. <a href="#fe74553d-2443-42af-bf51-d9dc058a400d-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/cross-dissolve-only-the-video-not-the-audio-in-final-cut-pro/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8689</post-id>	</item>
		<item>
		<title>Recording audio to an iPhone via a Tascam Portacapture X8</title>
		<link>https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/</link>
					<comments>https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 22 Jan 2026 06:04:04 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[audio recording]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Tascam Portacapture X8]]></category>
		<category><![CDATA[Undocumented]]></category>
		<category><![CDATA[USB audio]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8672</guid>

					<description><![CDATA[It is possible to use the Tascam Portacapture X8 as an ADC, input converter, and mixing board for an iPhone, but there&#8217;s a few things you&#8217;ll need to know. Thankfully, any USB-C or USB-C-to-Lightning cable will do You just connect one end of the USB-C cable to the Portacapture X8 (to its built-in USB port)&#8230; <a class="read-more-link" href="https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">It <em>is</em> possible to use the Tascam Portacapture X8 as an ADC, input converter, and mixing board for an iPhone, but there&#8217;s a few things you&#8217;ll need to know.</p>



<h3 class="wp-block-heading">Thankfully, any USB-C or USB-C-to-Lightning cable will do</h3>



<p class="wp-block-paragraph">You just connect one end of the USB-C cable to the Portacapture X8 (to its built-in USB port) and the other to your iPhone (USB-C or Lightning work, as suits your particular model of iPhone).</p>



<p class="wp-block-paragraph">The Portacapture X8 can both record audio via USB and output it.  It will automatically output it to any connected device that accepts it.  As far as I can tell it uses the master track mix (there doesn&#8217;t seem to be any way to send multiple tracks simultaneously &#8211; I assume the USB audio protocol only allows a plain stereo transmission).</p>



<h3 class="wp-block-heading">You must use 48kHz sampling</h3>



<p class="wp-block-paragraph">If you don&#8217;t, you&#8217;ll quickly get an error dialog on the Portacapture X8 saying &#8220;USB FS Mismatch&#8221;.  That&#8217;s its daft way of trying to say that the receiving USB device won&#8217;t accept the sampling frequency it&#8217;s outputting.  Why it can&#8217;t have a coherent error message, we&#8217;ll probably never know.</p>



<p class="wp-block-paragraph">You can set the sampling rate in the &#8220;General Settings&#8221; app, under &#8220;Rec Settings&#8221; (along with the file format and bit depth / rate, though it doesn&#8217;t seem to matter what those are set to w.r.t. iPhone compatibility &#8211; they affect only recording to the microSD card).</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="6034" height="8044" src="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch.webp" alt="" class="wp-image-8674" style="width:500px" srcset="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch.webp 6034w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-192x256.webp 192w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-768x1024.webp 768w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-1536x2048.webp 1536w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-192x256@2x.webp 384w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-1536x2048@2x.webp 3072w" sizes="auto, (max-width: 6034px) 100vw, 6034px" /></figure>
</div>


<h3 class="wp-block-heading">You don&#8217;t have to use the iPhone to provide power</h3>



<p class="wp-block-paragraph">By default the Portacapture X8 will try to use the iPhone to provide power, rather than its batteries. But it doesn&#8217;t trust USB power sources &#8211; it will ask, via a dialog, &#8220;Is the AC adapter 1.5A or more?&#8221;. On USB-C iPhones you <em>can</em> power the Portacapture X8 over USB &#8211; by selecting &#8220;Yes&#8221; &#8211; although it will drain your iPhone&#8217;s battery.  But whichever option you choose, the Portacapture X8 will then refuse to provide phantom power to your mics. If you&#8217;re not using phantom power then no worries, but if you are you must change the Portacapture&#8217;s settings &#8211; in &#8220;General Settings&#8221; app, under &#8220;Power/Display&#8221;, you must set &#8220;Power Source Select&#8221; to &#8220;Battery&#8221; instead of &#8220;Auto&#8221;. That will essentially turn off the Portacapture&#8217;s desire for power from USB, leaving USB as an audio channel only.  With it powering itself from its batteries, the phantom outputs will work like normal.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="6034" height="8044" src="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more.webp" alt="" class="wp-image-8673" style="width:500px" srcset="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more.webp 6034w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-192x256.webp 192w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-768x1024.webp 768w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-1536x2048.webp 1536w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-192x256@2x.webp 384w, https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-is-the-ac-adapter-1-5a-or-more-1536x2048@2x.webp 3072w" sizes="auto, (max-width: 6034px) 100vw, 6034px" /></figure>
</div>


<h3 class="wp-block-heading">You don&#8217;t have to record on the Portacapture X8</h3>



<p class="wp-block-paragraph">It passes audio through to the iPhone automatically, even if you&#8217;re not actively recording.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">Note: I vaguely recall having some issues with this <em>not</em> happening if you change some settings… it&#8217;s possible that things like &#8220;Pre Rec&#8221;, &#8220;Auto Rec&#8221;, or &#8220;Rec Pause&#8221; affect this.  I have all those off and it works as I&#8217;ve described.</p>
</div></div>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/recording-audio-to-an-iphone-via-a-tascam-portacapture-x8/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/01/tascam-portacapture-x8-error-dialog-usb-fs-mismatch-1536x2048.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8672</post-id>	</item>
		<item>
		<title>Fixing sudden, random iPhone disconnects from Image Capture</title>
		<link>https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/</link>
					<comments>https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 21 Jan 2026 18:33:09 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[Image Capture]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[Tethering]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8666</guid>

					<description><![CDATA[It appears that each time tethering is enabled or disabled on the iPhone, it disconnects Image Capture. So if you have spotty cellular service &#8211; because perhaps you live in the United States, where that&#8217;s the only kind of cellular service on offer &#8211; you might find that happens so often that you can&#8217;t complete&#8230; <a class="read-more-link" href="https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">It appears that each time tethering is enabled or disabled on the iPhone, it disconnects Image Capture.  So if you have spotty cellular service &#8211; because perhaps you live in the United States, where that&#8217;s the only kind of cellular service on offer &#8211; you might find that happens so often that you can&#8217;t complete basic media transfers in Image Capture.</p>



<p class="wp-block-paragraph">Thankfully the workaround is simple &#8211; disable tethering, or enable Airplane mode, while you&#8217;re using Image Capture.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/fixing-sudden-random-iphone-disconnects-from-image-capture/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2026/01/camera-disconnected-while-importing-error-dialog.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8666</post-id>	</item>
		<item>
		<title>Better image stabilisation in Final Cut Pro using Object Tracking</title>
		<link>https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/</link>
					<comments>https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 18 Jan 2026 20:22:54 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Final Cut Pro]]></category>
		<category><![CDATA[Object Tracking]]></category>
		<category><![CDATA[stabilisation]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8656</guid>

					<description><![CDATA[This is basically a short written set of instructions derived from Cody Wanner&#8216;s YouTube video on the topic, refined a little for simplicity and updated for the newer GUI in more recent versions of Final Cut Pro. So feel free to view that video if you prefer that medium. I just find I need to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is basically a short written set of instructions derived from <a href="https://www.youtube.com/@RCodyWanner" data-wpel-link="external" target="_blank" rel="external noopener">Cody Wanner</a>&#8216;s <a href="https://www.youtube.com/watch?v=JstYyYT4OzM" data-wpel-link="external" target="_blank" rel="external noopener">YouTube video on the topic</a>, refined a little for simplicity and updated for the newer GUI in more recent versions of Final Cut Pro.  So feel free to view that video if you prefer that medium.  I just find I need to reference this sporadically and it&#8217;s easier to just re-read written instructions that re-watch a whole video.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph"><strong>Background</strong>: Final Cut Pro&#8217;s built-in image stabilisation is a bit unreliable.  Sometimes it works perfectly, just like you&#8217;d expect.  Most of the time it requires manual tweaking and futzing in order to get good-enough results.  And sometimes it just <em>does not work</em>, no matter what you do, for reasons that are beyond me.  The technique shown here is annoyingly laborious to execute, but it works not just more reliably but also often just better (if your objective is <em>complete</em> stabilisation, at least).</p>
</div></div>



<p class="wp-block-paragraph">Steps:</p>



<ol class="wp-block-list">
<li>Duplicate the clip (option-drag it in the timeline view) and place the duplicate above<sup data-fn="6883a475-8404-4bfe-a857-098915a99cc4" class="fn"><a href="#6883a475-8404-4bfe-a857-098915a99cc4" id="6883a475-8404-4bfe-a857-098915a99cc4-link">1</a></sup> the original.  Ensure it&#8217;s perfectly aligned on the timeline&#8217;s X (time) axis, otherwise it won&#8217;t show up as a tracking source in step 3.2.</li>



<li>On the duplicate clip (make sure it&#8217;s selected and your playback position is within it so that you can see what you&#8217;re doing!):
<ol class="wp-block-list">
<li>Invert the scale (e.g. make it -100% instead of the default 100%).</li>



<li>Invert the X &amp; Y offsets (if necessary &#8211; if they were non-zero beforehand).</li>



<li>Add an Object Tracker (the + icon to the right of the &#8220;Tracker&#8221; titlebar at the bottom of the Video Inspector (right-hand pane)).</li>



<li>A white grid &#8211; your anchor section &#8211; should appear over your clip.  Move and resize it to have it cover an appropriate part of the clip (a subsection that&#8217;s contrasty and contains object(s) that are stable &#8211; in appearance and position &#8211; within the world space of the scene, and ideally are never obscured by anything during the clip).
<ul class="wp-block-list">
<li>☝️ You can adjust the timeline position of the clip to find the optimum frame in which to identify your anchor section.</li>
</ul>
</li>



<li>Click Analyze.
<ul class="wp-block-list">
<li>⚠️ This will sometimes not work if certain other operations are outstanding, such as dominant motion analysis for the clip &#8211; you have to either cancel those background tasks or wait for them to finish. One of Final Cut Pro&#8217;s many irritating bugs.</li>



<li>⚠️ Watch carefully as it works through the clip (forwards from your starting point, then backwards, as necessary), for:
<ul class="wp-block-list">
<li>Your anchor section being intruded upon by any moving objects within the scene.  If that happens, try to go back and refine your anchor section placement so that it won&#8217;t be intruded upon.  If that&#8217;s impossible, you can proceed but be aware that the results may be subpar.</li>



<li>The anchor section changing in position and size &#8211; the more it &#8216;wobbles&#8217;, the worse the final results are likely to be.  Consider different anchor section placement, or try a different analysis method (in the Video Inspector, for your Object Track, change the Analysis Method from the default, &#8220;Automatic&#8221;, to another option &#8211; note that you must click Analyze again after changing this, for it to take effect).</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>



<li>On the original clip:
<ol class="wp-block-list">
<li>Open the Transform viewer (the rectangle icon in the &#8220;Transform&#8221; titlebar in the Video Inspector).</li>



<li>Click the downward chevron next to &#8220;Tracker&#8221; text on the Tracker tab button (at the top of the video preview view), to bring up the configuration pop-up:
<ul class="wp-block-list">
<li>Set Tracker Source to your duplicate clip.</li>



<li>Set Tracker to the Object Tracker (or whatever you renamed it to).</li>



<li>Set the axes you want stabilisation to apply to (the Apply Tracker To checkboxes).</li>
</ul>
</li>
</ol>
</li>



<li>Disable the duplicate (e.g. V key while it&#8217;s selected).
<ul class="wp-block-list">
<li>☝️ You cannot have it disabled before you select it as the Tracker Source, as it won&#8217;t show up in the pop-up menu while disabled.</li>
</ul>
</li>



<li>On the original clip:
<ol class="wp-block-list">
<li>Set the scale to -100%.</li>



<li>Adjust the X &amp; Y positions to correct the framing, if necessary (if your X &amp; Y position were both 0 on the original clip, this shouldn&#8217;t be necessary).  Note that this usually isn&#8217;t a simple sign inversion as in similar previous steps.</li>
</ol>
</li>
</ol>



<p class="wp-block-paragraph">Optional sixth step:  file a bug report or suggestion with Apple asking for them to make their built-in image stabilisation work better, and/or make the object tracker GUI easier to use (it could be just one or two clicks, to enable it and say &#8220;lock this part of the scene in place&#8221;).</p>


<ol class="wp-block-footnotes"><li id="6883a475-8404-4bfe-a857-098915a99cc4">Technically it doesn&#8217;t matter if it&#8217;s above or below, I just find it slightly more convenient when it&#8217;s above as usually it&#8217;ll be disabled (showing the original, now-stabilised version from below) but I access it easily by simply enabling it (V key), such as if I want to adjust the tracking. <a href="#6883a475-8404-4bfe-a857-098915a99cc4-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/better-image-stabilisation-in-final-cut-pro-using-object-tracking/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8656</post-id>	</item>
		<item>
		<title>Extracting embedded images from a PDF</title>
		<link>https://wadetregaskis.com/extracting-embedded-images-from-a-pdf/</link>
					<comments>https://wadetregaskis.com/extracting-embedded-images-from-a-pdf/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 18 Jan 2026 18:50:33 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[Acrobat Reader]]></category>
		<category><![CDATA[Apple Preview]]></category>
		<category><![CDATA[AVIF]]></category>
		<category><![CDATA[File Juicer]]></category>
		<category><![CDATA[ImageMagick]]></category>
		<category><![CDATA[PDF]]></category>
		<category><![CDATA[pdfimages]]></category>
		<category><![CDATA[pdftoppm]]></category>
		<category><![CDATA[Poppler]]></category>
		<category><![CDATA[PPM]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[StuffIt]]></category>
		<category><![CDATA[The Unarchiver]]></category>
		<category><![CDATA[Zip]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8651</guid>

					<description><![CDATA[Surprisingly, the best way (that I&#8217;ve found) to do this is to use The Unarchiver, a free app from MacPaw (the folks behind SetApp and many other things). It seems to faithfully extract the images as-is, including ICC profiles (which might technically be separate from the image within the PDF, but nonetheless are crucial to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/extracting-embedded-images-from-a-pdf/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Surprisingly, the best way (that I&#8217;ve found) to do this is to use <a href="https://theunarchiver.com" data-wpel-link="external" target="_blank" rel="external noopener">The Unarchiver</a>, a free app from <a href="https://macpaw.com" data-wpel-link="external" target="_blank" rel="external noopener">MacPaw</a> (the folks behind <a href="https://setapp.com" data-wpel-link="external" target="_blank" rel="external noopener">SetApp</a> and many other things).  It seems to faithfully extract the images <em>as-is</em>, including ICC profiles (which might technically be separate from the image within the PDF, but nonetheless are crucial to the image being extracted correctly).</p>



<p class="wp-block-paragraph">The primary reason to extract the images <em>exactly</em> as is, bit-for-bit-identical, is that they&#8217;re typically already lossy-compressed (usually JPEG).  Recompressing them will introduce further losses or increase the file size<sup data-fn="548bb9e0-6fb6-4e03-a88c-0726bfb453fa" class="fn"><a href="#548bb9e0-6fb6-4e03-a88c-0726bfb453fa" id="548bb9e0-6fb6-4e03-a88c-0726bfb453fa-link">1</a></sup>, or both.</p>



<p class="wp-block-paragraph">Kudos to <a href="https://apple.stackexchange.com/users/97368/josef-habr" data-wpel-link="external" target="_blank" rel="external noopener">Josef Habr</a> for <a href="https://apple.stackexchange.com/a/477392/273188" data-wpel-link="external" target="_blank" rel="external noopener">suggesting The Unarchiver on StackExchange</a> &#8211; I would never have found it on my own, even though I already had it installed and use it occasionally (for more traditional archive file formats, like Zip or StuffIt).</p>



<p class="wp-block-paragraph">Frustratingly, Josef&#8217;s post aside, none of the recommendations you read online mention The Unarchiver, pointing instead to other options which are harder to install, harder to use, and don&#8217;t extract the images correctly.  Worst of all, many people falsely claim that their suggested approach will extract the images losslessly.  Examples include:</p>



<ul class="wp-block-list">
<li>pdfimages from <a href="https://poppler.freedesktop.org" data-wpel-link="external" target="_blank" rel="external noopener">Poppler</a> &#8211; silently re-encodes images in some cases (contrary to what its documentation and users claim), such as if they have non-sRGB colour profiles, <em>and</em> fails to preserve the embedded ICC profile.  Worse, <a href="https://gitlab.freedesktop.org/poppler/poppler/-/issues/526" data-wpel-link="external" target="_blank" rel="external noopener">the developers have known about this for nearly a decade and refuse to fix it</a>.</li>



<li>pdftoppm (et al) &#8211; explicitly convert the embedded images into another format, which while usually a lossless format (e.g. PNG or PPM) by default, still requires you to then re-encode them for use online etc.  Plus, they typically don&#8217;t preserve ICC profiles.</li>



<li><a href="https://imagemagick.org" data-wpel-link="external" target="_blank" rel="external noopener">ImageMagick</a> &#8211; doesn&#8217;t extract the images, merely renders the whole PDF page(s) as images, requiring further post-processing <em>and</em> inevitably reducing the image quality (due to mismatched output resolution and pixel alignment vs the embedded images&#8217;).</li>



<li>Exporting pages as images from Preview or Acrobat Reader &#8211; obviously doesn&#8217;t preserve the extracted images as-is, requires re-encoding them with additional compression losses, etc.</li>



<li>Screenshots via Preview or Acrobat Reader &#8211; ugh, I can&#8217;t even.</li>



<li>Various websites &#8211; I mean, they <em>might</em> work, but why upload your personal data to some skeezy website when it&#8217;s easy and fast to just use The Unarchiver locally?</li>
</ul>



<p class="wp-block-paragraph">I saw a recommendation for <a href="https://echoone.com/filejuicer/" data-wpel-link="external" target="_blank" rel="external noopener">File Juicer</a>, but unfortunately the free trial doesn&#8217;t work for me &#8211; it claims it&#8217;s already expired &#8211; so I was unable to check that it actually works.  Plus, it&#8217;s not free (USD$19 at time of writing) so that&#8217;s a strong disincentive compared to The Unarchiver.</p>


<ol class="wp-block-footnotes"><li id="548bb9e0-6fb6-4e03-a88c-0726bfb453fa">It <em>is</em> possible, with some newer formats like AVIF, to recompress a JPEG in a way that arguably <em>improves</em> the image quality while also reducing the file size.  AVIF encoders typically have some built-in smarts to recognise JPEG artefacts specifically, and try to remove them &#8211; a direct benefit visually since the artefacts are ugly and a benefit to [re]compression since the encoder then doesn&#8217;t need to waste time &amp; output bits trying to preserve the artefacts.<br><br>But, utilising this feature can require more care during compression to find the right trade-offs and ensure the result <em>is</em> in fact as good or better than the original &#8211; and in any case, AVIF recompression will work better from the original JPEG than a mangled version. <a href="#548bb9e0-6fb6-4e03-a88c-0726bfb453fa-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/extracting-embedded-images-from-a-pdf/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8651</post-id>	</item>
		<item>
		<title>Fading audio with AVPlayer</title>
		<link>https://wadetregaskis.com/fading-audio-with-avplayer/</link>
					<comments>https://wadetregaskis.com/fading-audio-with-avplayer/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 10 Dec 2025 16:29:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[AVAudioMix]]></category>
		<category><![CDATA[AVAudioMixInputParameters]]></category>
		<category><![CDATA[AVMutableAudioMix]]></category>
		<category><![CDATA[AVMutableAudioMixInputParameters]]></category>
		<category><![CDATA[AVPlayer]]></category>
		<category><![CDATA[fade]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8630</guid>

					<description><![CDATA[AVPlayer doesn&#8217;t provide a built-in way to fade in or out. I previously described how you achieve a video fade-in (or out) using general CoreAnimation layer animation, as part of making a macOS screen saver. Now let&#8217;s tackle the audio. I&#8217;m not certain what curve this implements, but to my ears it doesn&#8217;t sound quite&#8230; <a class="read-more-link" href="https://wadetregaskis.com/fading-audio-with-avplayer/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"><code>AVPlayer</code> doesn&#8217;t provide a built-in way to fade in or out.  I previously described how you achieve a <em>video</em> fade-in (or out) using general CoreAnimation layer animation, as <a href="https://wadetregaskis.com/how-to-make-a-macos-screen-saver/#Bonus_topic_fading_in" data-wpel-link="internal">part of making a macOS screen saver</a>.  Now let&#8217;s tackle the audio.</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">extension</span><span style="color: #000000"> </span><span style="color: #267F99">AVPlayer</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">fadeAudio</span><span style="color: #000000">(</span><span style="color: #795E26">from</span><span style="color: #000000"> </span><span style="color: #001080">startVolume</span><span style="color: #000000">: </span><span style="color: #267F99">Float</span><span style="color: #000000">, </span><span style="color: #795E26">to</span><span style="color: #000000"> </span><span style="color: #001080">endVolume</span><span style="color: #000000">: </span><span style="color: #267F99">Float</span><span style="color: #000000">, </span><span style="color: #795E26">duration</span><span style="color: #000000">: </span><span style="color: #267F99">Double</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> audioMix = </span><span style="color: #795E26">AVMutableAudioMix</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        audioMix.</span><span style="color: #001080">inputParameters</span><span style="color: #000000"> = (player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">?.</span><span style="color: #001080">tracks</span><span style="color: #000000"> ?? [])</span></span>
<span class="line"><span style="color: #000000">                                    .</span><span style="color: #795E26">compactMap</span><span style="color: #000000">(\.</span><span style="color: #001080">assetTrack</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">                                    .</span><span style="color: #795E26">filter</span><span style="color: #000000">({ </span><span style="color: #0000FF">$0</span><span style="color: #000000">.</span><span style="color: #001080">mediaType</span><span style="color: #000000"> == .</span><span style="color: #001080">audio</span><span style="color: #000000"> })</span></span>
<span class="line"><span style="color: #000000">                                    .</span><span style="color: #795E26">map</span><span style="color: #000000"> { track </span><span style="color: #AF00DB">in</span></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> currentTime = player.</span><span style="color: #795E26">currentTime</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> parameters = </span><span style="color: #795E26">AVMutableAudioMixInputParameters</span><span style="color: #000000">(</span><span style="color: #795E26">track</span><span style="color: #000000">: track)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            parameters.</span><span style="color: #795E26">setVolumeRamp</span><span style="color: #000000">(</span><span style="color: #795E26">fromStartVolume</span><span style="color: #000000">: startVolume,</span></span>
<span class="line"><span style="color: #000000">                                     </span><span style="color: #795E26">toEndVolume</span><span style="color: #000000">: endVolume,</span></span>
<span class="line"><span style="color: #000000">                                     </span><span style="color: #795E26">timeRange</span><span style="color: #000000">: </span><span style="color: #795E26">CMTimeRange</span><span style="color: #000000">(</span><span style="color: #795E26">start</span><span style="color: #000000">: currentTime,</span></span>
<span class="line"><span style="color: #000000">                                                            </span><span style="color: #795E26">duration</span><span style="color: #000000">: </span><span style="color: #795E26">CMTime</span><span style="color: #000000">(</span><span style="color: #795E26">seconds</span><span style="color: #000000">: duration,</span></span>
<span class="line"><span style="color: #000000">                                                                             </span><span style="color: #795E26">preferredTimescale</span><span style="color: #000000">: currentTime.</span><span style="color: #001080">timeScale</span><span style="color: #000000">)))</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #AF00DB">return</span><span style="color: #000000"> parameters</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">?.</span><span style="color: #001080">audioMix</span><span style="color: #000000"> = audioMix</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">I&#8217;m not certain what curve this implements, but to my ears it doesn&#8217;t sound quite as harsh as a naive linear ramp, so perhaps it&#8217;s an S-curve or similar.</p>



<h2 class="wp-block-heading">Edge cases not handled</h2>



<h3 class="wp-block-heading">Where there&#8217;s less than <code>duration</code> time left in the track(s).</h3>



<p class="wp-block-paragraph">How you want to handle that might vary depending on context. e.g. you could clamp the duration to the remaining duration (but you have to think about whether your individual tracks all have the same duration, whether they match the duration of the overall playback item, and whether they&#8217;re all aligned within the playback sequence), or wrap it around to the beginning (if you&#8217;re looping), or carry the fade through to the next item (if you&#8217;re playing a sequence of items), etc. Alas this must be left as an exercise to you, the reader.</p>



<h3 class="wp-block-heading">Starting a fade while another one is still in progress.</h3>



<p class="wp-block-paragraph">It will halt the previous fade, immediately jump to <code>startVolume</code>, and perform the new fade. If you know that a prior fade is in progress you could potentially extrapolate the current volume and start from there instead (though beware of non-linear ramping).</p>



<h3 class="wp-block-heading">If you move the playhead backwards (e.g. skimming, or looped playback).</h3>



<p class="wp-block-paragraph">The mix will stay in place, and result in wonky volume levels on the subsequent plays through. To work around that, you can add:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">?.</span><span style="color: #001080">audioMix</span><span style="color: #000000"> = </span><span style="color: #0000FF">nil</span></span>
<span class="line"><span style="color: #000000">player.</span><span style="color: #001080">volume</span><span style="color: #000000"> = endVolume</span></span></code></pre></div>



<p class="wp-block-paragraph">…wherever you restart playback at the beginning or move playback earlier than the end of the fade.</p>



<h2 class="wp-block-heading">Future work?</h2>



<p class="wp-block-paragraph">You <em>can</em> work around these limitations by doing a timer-based fade (i.e. <code>player.volume += smallIncrement</code> at regular, short intervals). However, the problem with that approach is that it&#8217;s not synchronised to actual playback &#8211; e.g. if the audio is paused, stutters, or faces an initial loading delay, your fade won&#8217;t wait for it, potentially resulting in no fade at all (e.g. it takes five seconds to buffer the audio before playback starts, at which point your five second &#8220;fade&#8221; has run to completion, so your audio starts playing abruptly at full volume).</p>



<p class="wp-block-paragraph">There&#8217;s very likely a third option that addresses <em>all</em> these shortcomings, but I explored that a bit and concluded that it&#8217;d be a lot more work.  If someone wants to explore that all the way, I&#8217;d be interested to see the result.  But for many purposes the above code is quite sufficient.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/fading-audio-with-avplayer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8630</post-id>	</item>
		<item>
		<title>How to loop video in AVPlayer</title>
		<link>https://wadetregaskis.com/how-to-loop-video-in-avplayer/</link>
					<comments>https://wadetregaskis.com/how-to-loop-video-in-avplayer/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 09 Dec 2025 17:42:00 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[AVPlayer]]></category>
		<category><![CDATA[AVPlayerItem]]></category>
		<category><![CDATA[AVPlayerItemDidPlayToEndTime]]></category>
		<category><![CDATA[AVPlayerLooper]]></category>
		<category><![CDATA[AVQueuePlayer]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8625</guid>

					<description><![CDATA[This is pretty rudimentary, but apparently our robot overlords need me to write this post because many of them suggested some truly bizarre approaches, some of which don&#8217;t work at all. If you&#8217;re using AVQueuePlayer, then just use AVPlayerLooper. Easy. But if for some reason you want to use AVPlayer specifically (e.g. you need to&#8230; <a class="read-more-link" href="https://wadetregaskis.com/how-to-loop-video-in-avplayer/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">This is pretty rudimentary, but apparently our robot overlords need me to write this post because many of them suggested some truly bizarre approaches, some of which don&#8217;t work at all.</p>



<p class="wp-block-paragraph">If you&#8217;re using <code><a href="https://developer.apple.com/documentation/avfoundation/avqueueplayer" data-wpel-link="external" target="_blank" rel="external noopener">AVQueuePlayer</a></code>, then just use <code><a href="https://developer.apple.com/documentation/avfoundation/avplayerlooper" data-wpel-link="external" target="_blank" rel="external noopener">AVPlayerLooper</a></code>.  Easy.  But if for some reason you want to use <code>AVPlayer</code> specifically (e.g. you need to do additional things anyway when playback loops back around), read on.</p>



<p class="wp-block-paragraph"><code>AVPlayer</code> itself doesn&#8217;t really help you here &#8211; it&#8217;s <code>AVPlayerItem</code> that you need to look at, as it has several notifications associated with it that can be very useful &#8211; most relevant here is<a href="https://developer.apple.com/documentation/avfoundation/avplayeritem/didplaytoendtimenotification" data-wpel-link="external" target="_blank" rel="external noopener"> </a>the <a href="https://developer.apple.com/documentation/avfoundation/avplayeritem/didplaytoendtimenotification" data-wpel-link="external" target="_blank" rel="external noopener">didPlayToEndTimeNotification</a>.  Simply observe that and restart the player explicitly, like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">let</span><span style="color: #000000"> player = </span><span style="color: #795E26">AVPlayer</span><span style="color: #000000">(…)  </span><span style="color: #008000">// You&#39;ll need to keep a reference to this somewhere, for use in the notification handler, as the notification doesn&#39;t provide it.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">void </span><span style="color: #795E26">startPlayer</span><span style="color: #000000">() {  </span><span style="color: #008000">// Or wherever / however you start playback.</span></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">playedToEnd</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">name</span><span style="color: #000000">: .</span><span style="color: #001080">AVPlayerItemDidPlayToEndTime</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">object</span><span style="color: #000000">: player.</span><span style="color: #001080">currentItem</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    player.</span><span style="color: #795E26">play</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">} </span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">playedToEnd</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">assert</span><span style="color: #000000">(player.</span><span style="color: #001080">currentItem</span><span style="color: #000000"> == notification.</span><span style="color: #001080">object</span><span style="color: #000000"> as? AVPlayerItem, </span><span style="color: #A31515">&quot;AVPlayer </span><span style="color: #0000FF">\(</span><span style="color: #000000FF">player</span><span style="color: #0000FF">)</span><span style="color: #A31515">&#39;s current item (</span><span style="color: #0000FF">\(</span><span style="color: #000000FF">player.</span><span style="color: #001080">currentItem</span><span style="color: #0000FF">)</span><span style="color: #A31515">) doesn&#39;t match the one in the notification object (</span><span style="color: #0000FF">\(</span><span style="color: #000000FF">notification.</span><span style="color: #001080">object</span><span style="color: #0000FF">)</span><span style="color: #A31515">).&quot;</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    player.</span><span style="color: #795E26">seek</span><span style="color: #000000">(</span><span style="color: #795E26">to</span><span style="color: #000000">: .</span><span style="color: #001080">zero</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    player.</span><span style="color: #795E26">play</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Again, considering using <code>AVQueuePlayer</code> if possible, but the above works too.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/how-to-loop-video-in-avplayer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8625</post-id>	</item>
		<item>
		<title>Image Capture import failing with error -9934</title>
		<link>https://wadetregaskis.com/image-capture-import-failing-with-error-9934/</link>
					<comments>https://wadetregaskis.com/image-capture-import-failing-with-error-9934/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sun, 07 Dec 2025 19:16:27 +0000</pubDate>
				<category><![CDATA[Ramblings]]></category>
		<category><![CDATA[-9934]]></category>
		<category><![CDATA[com.apple.ImageCaptureCore]]></category>
		<category><![CDATA[DNG]]></category>
		<category><![CDATA[HEIC]]></category>
		<category><![CDATA[Image Capture]]></category>
		<category><![CDATA[iPhone]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8601</guid>

					<description><![CDATA[The error &#8220;com.apple.ImageCaptureCore error -9934&#8221; during import (e.g. from an iPhone) can be caused by a lot of things, according to the Internet: Basically it seems like this &#8220;-9934&#8221; error is a catch-all for any kind of I/O error. Good job, Apple, in providing useful information to your users. But add to the aforementioned list&#8230; <a class="read-more-link" href="https://wadetregaskis.com/image-capture-import-failing-with-error-9934/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">The error &#8220;com.apple.ImageCaptureCore error -9934&#8221; during import (e.g. from an iPhone) can be caused by a lot of things, according to the Internet:</p>



<ul class="wp-block-list">
<li>Permission errors with the destination folder (e.g. not writable by the current user).</li>



<li>Conversion failures if you have the iPhone set to export images in &#8220;compatible&#8221; formats rather than their originals<sup data-fn="ecfdc392-955d-4750-ac97-c991a66441c7" class="fn"><a href="#ecfdc392-955d-4750-ac97-c991a66441c7" id="ecfdc392-955d-4750-ac97-c991a66441c7-link">1</a></sup></li>



<li>Unsupported image formats (e.g. HEIC) on very old versions of macOS (macOS 10.12 Sierra and earlier).</li>



<li>The destination folder not existing.</li>



<li>Etc.</li>
</ul>



<p class="wp-block-paragraph">Basically it seems like this &#8220;-9934&#8221; error is a catch-all for <em>any</em> kind of I/O error.  Good job, Apple, in providing useful information to your users.</p>



<p class="wp-block-paragraph">But add to the aforementioned list one other possibility, that I didn&#8217;t see mentioned <em>anywhere</em>:  the destination volume is full.</p>



<p class="wp-block-paragraph">I&#8217;ve been hitting this error message in Image Capture sporadically for months and been baffled by it &#8211; especially since it seemed to occur for seemingly random sets of images and videos, that were <em>sometimes</em> consistent between attempts and sometimes changed unpredictably.  Which is of course the result of the destination volume being perpetually close to full but varying slightly over time as random bits &amp; pieces are added or removed from it.</p>



<p class="wp-block-paragraph">It&#8217;s especially tricky because the destination volume won&#8217;t typically show as <em>actually</em> full, in the Finder &#8211; it could have virtually any amount of space left, even hundreds of gigabytes, because the particular image or video that hits the limit could be huge, and Image Capture either tries to pre-allocate the entire file&#8217;s space up front (failing immediately if there&#8217;s insufficient space) or retroactively zeroes out the file if it encounters a write error.  Either way, it leaves a bunch of zero-byte files (the failed transfers) behind, rather than incompletely-written ones, further hiding the fact that it ran out out of disk space.</p>


<ol class="wp-block-footnotes"><li id="ecfdc392-955d-4750-ac97-c991a66441c7">Please don&#8217;t ever do that.  It means transcoding your original images from DNG or HEIC into JPEGs, increasing their file size and/or reducing their quality in the process.  Don&#8217;t do it.  DNG and HEIC are old formats that are widely supported &#8211; not just on Apple devices, but Windows as well (and probably most Linux distros). <a href="#ecfdc392-955d-4750-ac97-c991a66441c7-link" aria-label="Jump to footnote reference 1">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/image-capture-import-failing-with-error-9934/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2025/12/image-capture-error-dialog-com-apple--imagecapturecore-error-9934.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8601</post-id>	</item>
		<item>
		<title>How to make a macOS screen saver</title>
		<link>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/</link>
					<comments>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/#comments</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Sat, 06 Dec 2025 22:46:22 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Howto]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bugs!]]></category>
		<category><![CDATA[com.apple.screensaver.willstop]]></category>
		<category><![CDATA[SaveHollywood]]></category>
		<category><![CDATA[Screen saver]]></category>
		<category><![CDATA[ScreenSaverView]]></category>
		<category><![CDATA[Snafu]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8580</guid>

					<description><![CDATA[First, make sure you really want to. macOS&#8217;s screen saver system is absurdly buggy and broken. It&#8217;s frustrating to work with and very difficult to make work right. If you&#8217;re determined, read on. Screen savers are basically just applications. Same bundle format and structure, just with a &#8220;saver&#8221; extension instead of &#8220;app&#8221;. Setting up the&#8230; <a class="read-more-link" href="https://wadetregaskis.com/how-to-make-a-macos-screen-saver/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">First, make sure you <em>really</em> want to.  macOS&#8217;s screen saver system is absurdly buggy and broken.  It&#8217;s frustrating to work with and very difficult to make work right.</p>



<p class="wp-block-paragraph">If you&#8217;re determined, read on.</p>



<p class="wp-block-paragraph">Screen savers are basically just applications.  Same bundle format and structure, just with a &#8220;saver&#8221; extension instead of &#8220;app&#8221;.</p>



<h2 class="wp-block-heading">Setting up the Xcode project</h2>



<p class="wp-block-paragraph">In Xcode, create a new project using the <code>Screen Saver</code> template.</p>



<p class="wp-block-paragraph">Delete the Objective-C code &amp; header files it creates by default (unless, I suppose, you want to write your screen saver in Objective-C &#8211; woo, retro! 😆).</p>



<p class="wp-block-paragraph">You need to import the ScreenSaver module (framework), subclass ScreenSaverView, and implement a couple of method overrides.  Here&#8217;s the basic skeleton:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import ScreenSaver

class MyScreenSaver: ScreenSaverView {
    override init?(frame: NSRect, isPreview: Bool) {
        super.init(frame: frame, isPreview: isPreview)
        setup()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setup()
    }

    private func setup() {
        // TODO
    }

    override func startAnimation() {
        super.startAnimation()

        // TODO
    }

    override func animateOneFrame() {  // Optional.
        // TODO
    }

    override func stopAnimation() { //  Only for the live preview in System Settings.
        // TODO

        super.stopAnimation()
    }

    override var hasConfigureSheet: Bool {
        true
    }

    private var configureSheetController: ConfigureSheetController?

    override var configureSheet: NSWindow? {
        configureSheetController = ConfigureSheetController(windowNibName: "ConfigureSheet")
        return configureSheetController?.window
    }
}

class ConfigureSheetController: NSWindowController {
    override var windowNibName: NSNib.Name? {
        return "ConfigureSheet"
    }

    override func windowDidLoad() {
        super.windowDidLoad()

        // TODO
    }

    @IBAction func okButtonClicked(_ sender: NSButton) {
        // TODO

        window!.sheetParent!.endSheet(window!, returnCode: .OK)
    }
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #AF00DB">import</span><span style="color: #000000"> </span><span style="color: #267F99">ScreenSaver</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">MyScreenSaver</span><span style="color: #000000">: ScreenSaverView {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">frame</span><span style="color: #000000">: NSRect, </span><span style="color: #795E26">isPreview</span><span style="color: #000000">: </span><span style="color: #267F99">Bool</span><span style="color: #000000">) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">frame</span><span style="color: #000000">: frame, </span><span style="color: #795E26">isPreview</span><span style="color: #000000">: isPreview)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">setup</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">required</span><span style="color: #000000"> </span><span style="color: #0000FF">init?</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000">: NSCoder) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #0000FF">init</span><span style="color: #000000">(</span><span style="color: #795E26">coder</span><span style="color: #000000">: coder)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #795E26">setup</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">startAnimation</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">animateOneFrame</span><span style="color: #000000">() {  </span><span style="color: #008000">// Optional.</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">() { </span><span style="color: #008000">//  Only for the live preview in System Settings.</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> hasConfigureSheet: </span><span style="color: #267F99">Bool</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">true</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> configureSheetController: ConfigureSheetController?</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> configureSheet: NSWindow? {</span></span>
<span class="line"><span style="color: #000000">        configureSheetController = </span><span style="color: #795E26">ConfigureSheetController</span><span style="color: #000000">(</span><span style="color: #795E26">windowNibName</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;ConfigureSheet&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> configureSheetController?.</span><span style="color: #001080">window</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">class</span><span style="color: #000000"> </span><span style="color: #267F99">ConfigureSheetController</span><span style="color: #000000">: NSWindowController {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> windowNibName: NSNib.Name? {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #A31515">&quot;ConfigureSheet&quot;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">windowDidLoad</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">windowDidLoad</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">@IBAction</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">okButtonClicked</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">sender</span><span style="color: #000000">: NSButton) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// TODO</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        window!.</span><span style="color: #001080">sheetParent</span><span style="color: #000000">!.</span><span style="color: #795E26">endSheet</span><span style="color: #000000">(window!, </span><span style="color: #795E26">returnCode</span><span style="color: #000000">: .</span><span style="color: #001080">OK</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<h2 class="wp-block-heading">Providing a preferences sheet</h2>



<p class="wp-block-paragraph">If you don&#8217;t have any options to configure, you can of course change <code>hasConfigureSheet</code> to return <code>false</code>.  Otherwise, you&#8217;ll need to create a &#8220;ConfigureSheet&#8221; xib file with a window in it containing your screen saver&#8217;s settings.  You can use UserDefaults to save your settings (if you wish), same as any other app.  And you&#8217;ll need to add an &#8220;Okay&#8221; or &#8220;Save&#8221; or similar button to dismiss the sheet.</p>



<h2 class="wp-block-heading">Getting ready to render</h2>



<p class="wp-block-paragraph">The key methods to implement are <code>setup</code>, with any initial configuration you wish to do (e.g. allocate image or video views, load assets, set up the view hierarchy, etc).</p>



<p class="wp-block-paragraph"><code>ScreenSaverView</code> is an <code>NSView</code> subclass with a flat black background by default, onto which you can add subviews.  Typically for a screen saver you have a very simple view hierarchy &#8211; often just a single view or CoreAnimation layer that you&#8217;re rendering to &#8211; but you can load a xib and insert elements from it into the view if you like.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">☝️ It&#8217;s wise to <em>not</em> render anything in <code>setup</code>, which includes accidentally &#8211; you might need to set views to hidden, layers to zero opacity, etc.  This is basically because there can be an arbitrarily long gap between <code>setup</code> and <code>startAnimation</code> calls, and it&#8217;s often weird to render something initially, potentially not animate for a noticeable length of time, and <em>then</em> start actually working properly.</p>



<p class="wp-block-paragraph">Alternatively, you might insert a placeholder image or text, e.g. &#8220;Loading…&#8221;, if you really want.  But in my opinion it&#8217;s more graceful to just let the initial black screen stand for a moment.</p>
</div></div>



<h2 class="wp-block-heading">Rendering</h2>



<p class="wp-block-paragraph"><code>startAnimation</code> is where you should actually start displaying things.  e.g. if you&#8217;re using an <code>AVPlayer</code>, this is where you actually start it playing (after making it visible, e.g. setting its opacity to 1).</p>



<p class="wp-block-paragraph"><code>animateOneFrame</code> is optional, and is only called if you set the <code>animationTimeInterval</code> property (on <code>self</code>) to a finite, non-zero value in <code>setup</code> (in which case it&#8217;ll be called at intervals <em>at least</em> that long &#8211; it might not be called as often as you desire if previous calls overrun or there&#8217;s other bottlenecks in the screen saver framework).  It&#8217;s essentially just a minor convenience vs having to explicitly set up an <code>NSTimer</code>.</p>



<p class="wp-block-paragraph">Given how buggy Apple&#8217;s screen saver framework is, I suggest <em>not</em> relying on <code>animateOneFrame</code> if you can at all avoid it.  Even if that means setting up your own timer.  That way when they likely break that too in some future macOS release, your screen saver won&#8217;t necessarily break as well.</p>



<h3 class="wp-block-heading">Bonus topic: fading in</h3>



<p class="wp-block-paragraph">Unless your screen saver inherently appears gently (e.g. starts rendering with a flat black view and only slowly adds to it), it&#8217;s nice to add a fade-in.  You can do that using CoreAnimation on the view&#8217;s layer:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>override func startAnimation() {
    // Other code…
    
    if let layer {
        layer.opacity = 0.0  // Should already be zero from `setup`, but just to be sure.

        let fadeAnimation = CABasicAnimation(keyPath: "opacity")
        fadeAnimation.fromValue = 0.0
        fadeAnimation.toValue = 1.0
        fadeAnimation.duration = 5  // Seconds.

        // Essential settings to keep the final state
        fadeAnimation.fillMode = .forwards
        fadeAnimation.isRemovedOnCompletion = false

        layer.add(fadeAnimation, forKey: "fadeAnimation")
    }
    
    // Other code…
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> layer {</span></span>
<span class="line"><span style="color: #000000">        layer.</span><span style="color: #001080">opacity</span><span style="color: #000000"> = </span><span style="color: #098658">0.0</span><span style="color: #000000">  </span><span style="color: #008000">// Should already be zero from `setup`, but just to be sure.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #0000FF">let</span><span style="color: #000000"> fadeAnimation = </span><span style="color: #795E26">CABasicAnimation</span><span style="color: #000000">(</span><span style="color: #795E26">keyPath</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;opacity&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">fromValue</span><span style="color: #000000"> = </span><span style="color: #098658">0.0</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">toValue</span><span style="color: #000000"> = </span><span style="color: #098658">1.0</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">duration</span><span style="color: #000000"> = </span><span style="color: #098658">5</span><span style="color: #000000">  </span><span style="color: #008000">// Seconds.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #008000">// Essential settings to keep the final state</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">fillMode</span><span style="color: #000000"> = .</span><span style="color: #001080">forwards</span></span>
<span class="line"><span style="color: #000000">        fadeAnimation.</span><span style="color: #001080">isRemovedOnCompletion</span><span style="color: #000000"> = </span><span style="color: #0000FF">false</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        layer.</span><span style="color: #795E26">add</span><span style="color: #000000">(fadeAnimation, </span><span style="color: #795E26">forKey</span><span style="color: #000000">: </span><span style="color: #A31515">&quot;fadeAnimation&quot;</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Note that you cannot implement a fade-out when the screen saver exits, because macOS hides your screen saver immediately.  Plus, the user might not want a fade-out as they may be in a rush to do something on their computer.</p>



<h3 class="wp-block-heading">Previewing</h3>



<p class="wp-block-paragraph">You can determine if you&#8217;re running for real or only the preview via the <code>isPreview</code> property (on <code>self</code>).  Many screen savers don&#8217;t care, but particularly if you save any persistent state, you might want to avoid doing that during preview.  For example, in a screen saver which plays a looping video and resumes where it last left off, you probably don&#8217;t want the preview to quietly advance the video.</p>



<h2 class="wp-block-heading">Stopping</h2>



<p class="wp-block-paragraph"><code>stopAnimation</code> is <em>only</em> used for the live preview thumbnail shown in the Screen Saver System Settings pane.  It is <em>never</em> called in normal operation of the screen saver (contrary to what Apple&#8217;s documentation says &#8211; Apple broke that in macOS Sonoma and later).</p>



<p class="wp-block-paragraph">And that leads to the first path off the official track.  When the screen saver is dismissed by the user, <em>nothing</em> in Apple&#8217;s framework code does anything.  Your view continues to exist, <code>animateOneFrame</code> continues getting called, etc.  Your screen saver just runs in the background, its output not visible, but wasting CPU cycles and RAM.  Worse, if you have sound, that keeps playing.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">🙏 A big thanks to <a href="https://stackoverflow.com/users/4103152/cwizou" data-wpel-link="external" target="_blank" rel="external noopener">cwizou</a> via <a href="https://stackoverflow.com/questions/66861833/audio-keeps-playing-after-screensaver-ends" data-wpel-link="external" target="_blank" rel="external noopener">StackOverflow</a> for documenting <a href="https://stackoverflow.com/a/67161635" data-wpel-link="external" target="_blank" rel="external noopener">the solution</a>, which I&#8217;ve summarised below.</p>
</div></div>



<p class="wp-block-paragraph">To get around that, you need to register for the <code>com.apple.screensaver.willstop</code> notification, in <code>setup</code>, like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>  private func setup() {
    DistributedNotificationCenter.default.addObserver(self,
                                                      selector: #selector(willStop(_:)),
                                                      name: Notification.Name("com.apple.screensaver.willstop"),
                                                      object: nil)
}
    
@objc func willStop(_ notification: Notification) {
    stopAnimation()
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #000000">  </span><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    DistributedNotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">willStop</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">name</span><span style="color: #000000">: Notification.</span><span style="color: #795E26">Name</span><span style="color: #000000">(</span><span style="color: #A31515">&quot;com.apple.screensaver.willstop&quot;</span><span style="color: #000000">),</span></span>
<span class="line"><span style="color: #000000">                                                      </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">willStop</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Note that you still need <code>stopAnimation</code> specifically, because in the live preview in System Settings you won&#8217;t receive that <code>com.apple.screensaver.willstop</code> notification (from the system&#8217;s point of view, the screen saver <em>isn&#8217;t</em> running &#8211; it&#8217;s merely previewing).</p>



<h2 class="wp-block-heading">Handling resumption</h2>



<p class="wp-block-paragraph">Here&#8217;s the second big bug in Apple&#8217;s screen saver framework &#8211; every time the screen saver starts, your <code>ScreenSaverView</code> subclass is created again. But the old one doesn&#8217;t go anywhere. So now you have <em>two</em> copies running simultaneously, which is at the very least wasteful, and can easily lead to gnarly bugs and weird behaviour (e.g. if both are playing sound, or both modify persistent state).</p>



<p class="wp-block-paragraph">There are essentially two ways to handle this:</p>



<ol class="wp-block-list">
<li>Kill your own process every time you stop animating.</li>



<li>Manually kill or lame-duck older views when a new one is initialised.</li>
</ol>



<p class="wp-block-paragraph">Note that you <em>cannot</em> simply check at <code>MyScreenSaver</code> initialisation time if an instance already exists and if so fail initialisation (as is prescribed by <a href="https://zsmb.co/building-a-macos-screen-saver-in-kotlin/#macos-sonoma" data-wpel-link="external" target="_blank" rel="external noopener">this</a> otherwise excellent write-up of this problem), because if you don&#8217;t correctly initialise you&#8217;ll sometimes end up with <em>nothing</em> rendering or running (the screen saver framework appears to not gracefully handle initialisation failures).</p>



<p class="wp-block-paragraph">Killing your own process can work but has some perils:</p>



<ul class="wp-block-list">
<li>If you kill your process in <code>stopAnimation</code> the screen will flash black momentarily before actually exiting screen saver mode, which is visually annoying.</li>



<li>If the screen saver is restarted rapidly after being interrupted, sometimes you&#8217;ll end up with nothing but a black screen (with no screen saver running).  There&#8217;s evidently some race condition in Apple&#8217;s screen saver system between screen saver processes exiting and being [re]launched.</li>
</ul>



<p class="wp-block-paragraph">So I recommend not taking that approach.  Instead, you can lame-duck the old view instances.  They&#8217;ll stick around, which is a little wasteful of RAM, but as long as they&#8217;re not rendering or otherwise doing anything, they&#8217;re benign.</p>



<p class="wp-block-paragraph">There are various ways to implement that, but one of the simpler ones is simply a notification between instances:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>static let NewInstanceNotification = "com.myapp.MyScreenSaver.NewInstance";

var lameDuck = false

private func setup() {
    // Initial setup…
    
    NotificationCenter.default.post(name: MyScreenSaver.NewInstanceNotification, object: self)

    NotificationCenter.default.addObserver(self,
                                           selector: #selector(neuter(_:)),
                                           name: MyScreenSaver.NewInstanceNotification,
                                           object: nil)

    // Further setup…
}

@objc func neuter(_ notification: Notification) {
    lameDuck = true

    stopAnimation()

    self.removeFromSuperview()

    // TODO: any additional cleanup you can, e.g. release image &amp; video files, throw out transient models and state, etc.

    NotificationCenter.default.removeObserver(self)
    DistributedNotificationCenter.default().removeObserver(self)
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">static</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> NewInstanceNotification = </span><span style="color: #A31515">&quot;com.myapp.MyScreenSaver.NewInstance&quot;</span><span style="color: #000000">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">var</span><span style="color: #000000"> lameDuck = </span><span style="color: #0000FF">false</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">private</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">setup</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Initial setup…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">post</span><span style="color: #000000">(</span><span style="color: #795E26">name</span><span style="color: #000000">: MyScreenSaver.</span><span style="color: #001080">NewInstanceNotification</span><span style="color: #000000">, </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">addObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">selector</span><span style="color: #000000">: </span><span style="color: #795E26">#selector</span><span style="color: #000000">(</span><span style="color: #795E26">neuter</span><span style="color: #000000">(_:)),</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">name</span><span style="color: #000000">: MyScreenSaver.</span><span style="color: #001080">NewInstanceNotification</span><span style="color: #000000">,</span></span>
<span class="line"><span style="color: #000000">                                           </span><span style="color: #795E26">object</span><span style="color: #000000">: </span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Further setup…</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">@objc</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">neuter</span><span style="color: #000000">(</span><span style="color: #795E26">_</span><span style="color: #000000"> </span><span style="color: #001080">notification</span><span style="color: #000000">: Notification) {</span></span>
<span class="line"><span style="color: #000000">    lameDuck = </span><span style="color: #0000FF">true</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #795E26">removeFromSuperview</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// TODO: any additional cleanup you can, e.g. release image &amp; video files, throw out transient models and state, etc.</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">    NotificationCenter.</span><span style="color: #001080">default</span><span style="color: #000000">.</span><span style="color: #795E26">removeObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">    DistributedNotificationCenter.</span><span style="color: #795E26">default</span><span style="color: #000000">().</span><span style="color: #795E26">removeObserver</span><span style="color: #000000">(</span><span style="color: #0000FF">self</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">You should check <code>lameDuck</code> at the start of methods like <code>startAnimation</code> or <code>animateOneFrame</code> and exit immediately if it&#8217;s set to <code>true</code>.  Unfortunately, Apple&#8217;s screen saver framework will still call those methods on old instances.</p>



<h2 class="wp-block-heading">Exiting</h2>



<p class="wp-block-paragraph">Unfortunately Apple&#8217;s screen saver system will never terminate your screen saver process.  Worse, even if you do <em>nothing</em> yourself, Apple&#8217;s screen saver framework code will run in an infinite loop, wasting [a small amount of] CPU time.  So it&#8217;s not great to leave your screen saver process running indefinitely.</p>



<p class="wp-block-paragraph">Thus, I implement an idle timeout in my screen savers, to have them exit if they&#8217;re not active for a while.  This can be done like:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>@MainActor var idleTimeoutWorkItem: DispatchWorkItem? = nil

override func startAnimation() {
    // Other code…
    
    DispatchQueue.main.async {
        if let idleTimeoutWorkItem {
            idleTimeoutWorkItem.cancel()
        }

        idleTimeoutWorkItem = nil
    }
    
    // Other code…
}

override func stopAnimation() {
    // Other code…
    
    if !lameDuck {
        DispatchQueue.main.async {
            idleTimeoutWorkItem?.cancel()

            let workItem = DispatchWorkItem(block: {
                NSApplication.shared.terminate(nil)
            })
            
            idleTimeoutWorkItem = workItem
            
            DispatchQueue.main.asyncAfter(wallDeadline: .now() + 65, execute: workItem)
        }
    }
    
    // Other code…
}</textarea></pre><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">@MainActor</span><span style="color: #000000"> </span><span style="color: #0000FF">var</span><span style="color: #000000"> idleTimeoutWorkItem: DispatchWorkItem? = </span><span style="color: #0000FF">nil</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">startAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">async</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">if</span><span style="color: #000000"> </span><span style="color: #0000FF">let</span><span style="color: #000000"> idleTimeoutWorkItem {</span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem.</span><span style="color: #795E26">cancel</span><span style="color: #000000">()</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">        idleTimeoutWorkItem = </span><span style="color: #0000FF">nil</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">stopAnimation</span><span style="color: #000000">() {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> !lameDuck {</span></span>
<span class="line"><span style="color: #000000">        DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #001080">async</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem?.</span><span style="color: #795E26">cancel</span><span style="color: #000000">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #000000">            </span><span style="color: #0000FF">let</span><span style="color: #000000"> workItem = </span><span style="color: #795E26">DispatchWorkItem</span><span style="color: #000000">(</span><span style="color: #795E26">block</span><span style="color: #000000">: {</span></span>
<span class="line"><span style="color: #000000">                NSApplication.</span><span style="color: #001080">shared</span><span style="color: #000000">.</span><span style="color: #795E26">terminate</span><span style="color: #000000">(</span><span style="color: #0000FF">nil</span><span style="color: #000000">)</span></span>
<span class="line"><span style="color: #000000">            })</span></span>
<span class="line"><span style="color: #000000">            </span></span>
<span class="line"><span style="color: #000000">            idleTimeoutWorkItem = workItem</span></span>
<span class="line"><span style="color: #000000">            </span></span>
<span class="line"><span style="color: #000000">            DispatchQueue.</span><span style="color: #001080">main</span><span style="color: #000000">.</span><span style="color: #795E26">asyncAfter</span><span style="color: #000000">(</span><span style="color: #795E26">wallDeadline</span><span style="color: #000000">: .</span><span style="color: #795E26">now</span><span style="color: #000000">() + </span><span style="color: #098658">65</span><span style="color: #000000">, </span><span style="color: #795E26">execute</span><span style="color: #000000">: workItem)</span></span>
<span class="line"><span style="color: #000000">        }</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #008000">// Other code…</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p class="wp-block-paragraph">I chose the 65 second timeout somewhat arbitrarily.  I figure there&#8217;s a reasonable chance a user will unlock their screen to do something quick, then engage the screen saver again &#8211; all in the less than a minute &#8211; and the cost of idling in the background for an extra minute is small, compared to the cost of relaunching the whole app and reinitialising your renderer.</p>



<p class="wp-block-paragraph">I added five extra seconds to reduce the probability of aligning with some one-minute timer (e.g. a spurious wake with the screen saver set to start automatically after one minute of no user activity).</p>



<p class="wp-block-paragraph">You can adjust it however you like.</p>



<h2 class="wp-block-heading">Testing your screen saver</h2>



<p class="wp-block-paragraph">Double-clicking the built product (your &#8220;.saver&#8221; app) will prompt the user to install it, replacing an old version if necessary.  So that works, though I find it faster to just manually copy the &#8220;.saver&#8221; app to <code>~/Library/Screen Savers</code>.  Just make sure to kill the existing <code>legacyScreenSaver</code> process, if necessary.</p>



<p class="wp-block-paragraph">You can test it in System Settings, in the Screen Saver pane.  That&#8217;s the only place you can test the live preview part.</p>



<p class="wp-block-paragraph">But otherwise, I found it easiest to just set one of the screen hot corners to start the screen saver, and use that immediately after copying the new &#8220;.saver&#8221; file into place.</p>



<p class="wp-block-paragraph">Just be aware that the first time any new copy of the screen saver runs, macOS runs a verification on the bundle, which can take a while if your screen saver is non-trivial in size (e.g. if you bundle large image or video resources).  You&#8217;ll get a black screen with nothing happening, after invoking the screen saver, while that verification is running.</p>



<h2 class="wp-block-heading">Distributing your screen saver</h2>



<p class="wp-block-paragraph">You don&#8217;t <em>have</em> to sign your screen saver, necessarily, but users will get some annoying error dialogs trying to run it, and will have to fiddle with things in System Settings &#8211; or, if they&#8217;re on a corporate Mac, they might not be able to run it at all.  So it&#8217;s preferable to just sign it.</p>



<p class="wp-block-paragraph">Xcode doesn&#8217;t support signing screen savers like it does for plain app targets and the like.  So you have to do it manually via the command line, on the built product (your &#8220;.saver&#8221; app).  Thankfully it&#8217;s just two commands (once you have the appropriate stuff set up in your developer account &#8211; note that you will need a paid Apple Developer account, at $99/year).</p>



<p class="wp-block-paragraph">Follow the instructions <a href="https://www.gabrieluribe.me/blog/how-to-distribute-a-screensaver-on-macos-2022" data-wpel-link="external" target="_blank" rel="external noopener">here</a>, but note that <a href="https://wadetregaskis.com/apples-notarytool-cant-handle-zip64-files/" data-type="post" data-id="9164" data-wpel-link="internal">you should use DMGs, not Zip files, because of bugs in Apple&#8217;s server-side infrastructure</a>, and also that those instructions are missing the final step:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph">xcrun stapler staple -v MyScreenSaver.saver</p>
</blockquote>



<p class="wp-block-paragraph">Note that you run it against the screen saver itself, <em>not</em> the DMG. The DMG&#8217;s just a hack to get Apple&#8217;s notary tool to accept the submission. It&#8217;s the screen saver bundle itself that&#8217;s actually notarised and signed.</p>



<div class="wp-block-group"><div class="wp-block-group__inner-container is-layout-constrained wp-block-group-is-layout-constrained">
<p class="wp-block-paragraph">⚠️ <code>notarytool</code> uploads your screen saver to Apple.  Be sure it doesn&#8217;t contain anything you&#8217;re not happy being potentially public (Apple will <em>presumably</em> try to keep your uploads private to Apple, and <em>might</em> not intentionally store them forever, but I wouldn&#8217;t bet my life on their confidentiality).</p>
</div></div>



<h2 class="wp-block-heading">Conclusion</h2>



<p class="wp-block-paragraph">That&#8217;s &#8220;it&#8221; in a superficial sense &#8211; if you&#8217;ve followed all this so far, you have a <em>roughly</em> working screen saver.</p>



<p class="wp-block-paragraph">But there are a lot more bugs and nuances thereof that may afflict you, depending on what you&#8217;re doing in your screen saver.  So good luck. 😣</p>



<h2 class="wp-block-heading">Addendum: SaveHollywood</h2>



<p class="wp-block-paragraph">I found that looking at existing open source screen savers was <em>partially</em> helpful, but also sometimes misleading.  e.g. for my most recent screen saver I basically just play a video in a loop (which <em>should</em> be embarrassingly trivial but took two weeks to get working properly, thanks to the aforementioned bugs in Apple&#8217;s frameworks, among many others).  In that case I looked at <a href="https://github.com/packagesdev/savehollywood" data-wpel-link="external" target="_blank" rel="external noopener">SaveHollywood</a>, a similar screen saver, for aid and ideas.</p>



<p class="wp-block-paragraph">Unfortunately, SaveHollywood is abandoned and doesn&#8217;t work on recent versions of macOS.  The way it does some things is archaic and either not the best way or not functional at all.</p>



<p class="wp-block-paragraph">Nonetheless, it did help with some of the higher-level aspects, above the screen saver machinery itself, like how to use <code>AVPlayer</code> in a screen saver.</p>



<p class="wp-block-paragraph">So, <em>do</em> check out similar, existing screen savers (and of course just use them directly if they suit your needs!) but beware of obsolete or otherwise incorrect code.</p>



<h2 class="wp-block-heading">Addendum:  What this says about Apple</h2>



<p class="wp-block-paragraph">What really troubles me about the screen saver system is what it says about Apple&#8217;s approach to the Mac, software, and their users.  Which is sadly just the same thing we&#8217;ve been seeing for years now.</p>



<p class="wp-block-paragraph">Screen savers used to work fine.  There was an API established <em>long</em> ago that was lightweight, straight-forward, and effective.  All Apple had to do was <em>not break it</em>.</p>



<p class="wp-block-paragraph">And <em>how</em> they broke it is troubling.  Perhaps it was prompted by some otherwise unrelated but well-meaning refactor &#8211; pulling screen saver code out into a separate, sandboxed process, perhaps, for improved system security.  Fine.  But if it&#8217;s worth changing it&#8217;s worth changing <em>properly</em>.  It&#8217;s very clear that whomever did the changes either (a) didn&#8217;t care that they broke things badly, or (b) didn&#8217;t care to check.</p>



<p class="wp-block-paragraph">It&#8217;s that recurring theme of <em>not caring</em> that&#8217;s most disappointing in today&#8217;s Apple.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/how-to-make-a-macos-screen-saver/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2025/12/screen-saver-system-settings-panel-with-my-screen-saver-selected.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8580</post-id>	</item>
		<item>
		<title>32-bit float audio recording is not a panacea</title>
		<link>https://wadetregaskis.com/32-bit-float-audio-recording-is-not-a-panacea/</link>
					<comments>https://wadetregaskis.com/32-bit-float-audio-recording-is-not-a-panacea/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Thu, 31 Jul 2025 03:18:00 +0000</pubDate>
				<category><![CDATA[Education]]></category>
		<category><![CDATA[Reviews]]></category>
		<category><![CDATA[32-bit float audio]]></category>
		<category><![CDATA[audio]]></category>
		<category><![CDATA[lies]]></category>
		<category><![CDATA[Portacapture X8]]></category>
		<category><![CDATA[Sennheiser K6]]></category>
		<category><![CDATA[Sennheiser ME65]]></category>
		<category><![CDATA[Sennheiser ME66]]></category>
		<category><![CDATA[Tascam]]></category>
		<category><![CDATA[Tested]]></category>
		<category><![CDATA[Zoom H4n]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8524</guid>

					<description><![CDATA[I recently replaced a horrible, dodgy Zoom H4n with a Tascam Portacapture X8, for recording (primarily) theatre and music performances. One of the appeals was 32-bit floating-point recording which was literally promised to eliminate concerns about input levelling, clipping, and noise: The reality is, with 32-bit float recording you can turn on your recorder, hit&#8230; <a class="read-more-link" href="https://wadetregaskis.com/32-bit-float-audio-recording-is-not-a-panacea/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">I recently replaced a horrible, dodgy <a href="https://zoomcorp.com/en/us/handheld-recorders/handheld-recorders/h4n/" data-wpel-link="external" target="_blank" rel="external noopener">Zoom H4n</a> with a <a href="https://tascam.com/us/product/portacapture_x8" data-wpel-link="external" target="_blank" rel="external noopener">Tascam Portacapture X8</a>, for recording (primarily) theatre and music performances. One of the appeals was 32-bit floating-point recording which was literally promised to eliminate concerns about input levelling, clipping, and noise:</p>



<figure class="wp-block-pullquote is-style-default"><blockquote><p>The reality is, with 32-bit float recording you can turn on your recorder, hit record, and be 100% confident that <strong>you&#8217;ll be capturing high-fidelity, low-noise audio, without ever adjusting your input level</strong>.</p><cite><a href="https://tascam.jp/int/feature/32-bit_float" data-wpel-link="external" target="_blank" rel="external noopener">Why 32-bit Float Recording</a>, Tascam</cite></blockquote></figure>



<figure class="wp-block-pullquote"><blockquote><p>…the huge dynamic range that 32-bit float offers means your audio is always captured well above the noise floor, and also <strong>makes it basically impossible to distort due to high input levels</strong>.</p><cite><a href="https://tascam.jp/int/feature/32-bit_float" data-wpel-link="external" target="_blank" rel="external noopener">Why 32-bit Float Recording</a>, Tascam</cite></blockquote></figure>



<p class="wp-block-paragraph">Five minutes of some trivial testing shows that this is just not true.</p>



<h2 class="wp-block-heading">Noise is still affected by input gain</h2>



<p class="wp-block-paragraph">Here&#8217;s a composed recording of four 3-second clips recording the room tone in my office. They are (in order): Auto gain, 57dB, 35dB, 0dB:</p>


<div
    id="h5ap-player-1"
    data-id="h5ap-player-1"
    data-attributes="{&quot;uniqueId&quot;:&quot;h5apc2f6cbfb&quot;,&quot;source&quot;:&quot;https:\/\/wadetregaskis.com\/wp-content\/uploads\/2025\/07\/Noise.wav&quot;,&quot;primaryColor&quot;:&quot;#4A5464&quot;,&quot;controlColor&quot;:&quot;#4A5464&quot;,&quot;bgColor&quot;:&quot;#F5F5F5&quot;,&quot;loader&quot;:true,&quot;preload&quot;:&quot;auto&quot;,&quot;radius&quot;:&quot;50px&quot;,&quot;waveType&quot;:&quot;equalizer&quot;,&quot;clientId&quot;:&quot;&quot;,&quot;align&quot;:&quot;&quot;,&quot;alignment&quot;:&quot;left&quot;,&quot;poster&quot;:&quot;&quot;,&quot;title&quot;:&quot;audio title&quot;,&quot;artist&quot;:&quot;&quot;,&quot;color&quot;:&quot;#87ceeb&quot;,&quot;hoverColor&quot;:&quot;#00B3FF&quot;,&quot;skin&quot;:&quot;Default&quot;,&quot;repeat&quot;:false,&quot;autoplay&quot;:false,&quot;muted&quot;:false,&quot;saveState&quot;:false,&quot;disablePause&quot;:false,&quot;seekTime&quot;:10,&quot;startTime&quot;:0,&quot;download&quot;:true,&quot;width&quot;:&quot;100%&quot;,&quot;speed&quot;:{&quot;selected&quot;:1,&quot;speed&quot;:[&quot;0.5&quot;,&quot; 0.75&quot;,&quot; 1&quot;,&quot; 1.25&quot;,&quot; 1.5&quot;,&quot; 1.75&quot;,&quot; 2&quot;,&quot; 4&quot;,&quot; 8&quot;]},&quot;controls&quot;:{&quot;restart&quot;:false,&quot;rewind&quot;:false,&quot;play&quot;:true,&quot;fast-forward&quot;:false,&quot;progress&quot;:true,&quot;duration&quot;:false,&quot;current-time&quot;:true,&quot;mute&quot;:true,&quot;volume&quot;:true,&quot;settings&quot;:true,&quot;download&quot;:false},&quot;options&quot;:{&quot;volume&quot;:0.5},&quot;isSticky&quot;:false,&quot;CSS&quot;:&quot;&quot;,&quot;multiple_audio&quot;:false,&quot;lazyLoad&quot;:false,&quot;defaultValue&quot;:{&quot;Default&quot;:{&quot;primaryColor&quot;:&quot;#4A5464&quot;,&quot;controlColor&quot;:&quot;#4A5464&quot;,&quot;bgColor&quot;:&quot;#F5F5F5&quot;},&quot;Fusion&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;controlColor&quot;:&quot;#fff&quot;},&quot;Stamp&quot;:{&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;},&quot;Wave&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Card-1&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;controlColor&quot;:&quot;#161616&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Card-2&quot;:{&quot;primaryColor&quot;:&quot;#00FBF3&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Simple-1&quot;:{&quot;primaryColor&quot;:&quot;#00dcff&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#00dcff&quot;},&quot;Simple-2&quot;:{&quot;primaryColor&quot;:&quot;#06F7FF00&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;},&quot;Player9&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#EDEFF2&quot;},&quot;Player10&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#EDEFF2&quot;},&quot;Player11&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;}},&quot;i18n&quot;:{&quot;restart&quot;:&quot;Restart&quot;,&quot;rewind&quot;:&quot;Rewind {seektime}s&quot;,&quot;play&quot;:&quot;Play&quot;,&quot;pause&quot;:&quot;Pause&quot;,&quot;fastForward:&quot;:&quot;Forward {seektime}s&quot;,&quot;seek&quot;:&quot;Seek&quot;,&quot;seekLabel&quot;:&quot;{currentTime} of {duration}&quot;,&quot;played&quot;:&quot;Played&quot;,&quot;buffered&quot;:&quot;Buffered&quot;,&quot;currentTime:&quot;:&quot;Current time&quot;,&quot;duration&quot;:&quot;Duration&quot;,&quot;volume&quot;:&quot;Volume&quot;,&quot;mute&quot;:&quot;Mute&quot;,&quot;unmute&quot;:&quot;Unmute&quot;,&quot;enableCaptions&quot;:&quot;Enable captions&quot;,&quot;disableCaptions&quot;:&quot;Disable captions&quot;,&quot;download&quot;:&quot;Download&quot;,&quot;enterFullscreen&quot;:&quot;Enter fullscreen&quot;,&quot;exitFullscreen&quot;:&quot;Exit fullscreen&quot;,&quot;frameTitle&quot;:&quot;Player for {title}&quot;,&quot;captions&quot;:&quot;Captions&quot;,&quot;settings&quot;:&quot;Settings&quot;,&quot;pip&quot;:&quot;PIP&quot;,&quot;menuBack&quot;:&quot;Go back to previous menu&quot;,&quot;speed&quot;:&quot;Speed&quot;,&quot;normal&quot;:&quot;Normal&quot;,&quot;quality&quot;:&quot;Quality&quot;,&quot;loop&quot;:&quot;Loop&quot;,&quot;start&quot;:&quot;Start&quot;,&quot;end&quot;:&quot;End&quot;,&quot;all&quot;:&quot;All&quot;,&quot;reset&quot;:&quot;Reset&quot;,&quot;disabled&quot;:&quot;Disabled&quot;,&quot;enabled&quot;:&quot;Enabled&quot;,&quot;advertisement&quot;:&quot;Ad&quot;,&quot;qualityBadge&quot;:{&quot;2160&quot;:&quot;4K&quot;,&quot;1440&quot;:&quot;HD&quot;,&quot;1080&quot;:&quot;HD&quot;,&quot;720&quot;:&quot;HD&quot;,&quot;576&quot;:&quot;SD&quot;,&quot;480&quot;:&quot;SD&quot;}}}"
    class="wp-block-h5ap-tailwind wp-block-h5ap-audioplayer">
            <div class='h5ap_lp'>
            <div class='bar bar-1'></div>
            <div class='bar bar-1'></div>
        </div>
    </div>


<p class="wp-block-paragraph">As you can hear, auto and maximum input gain in this case have very similar noise levels (which is to say, perceptually none), but as you reduce the recorder&#8217;s input gain (and instead apply the gain in post) the noise increases substantially and becomes very noticeable.</p>



<p class="wp-block-paragraph">Granted this is a very big gain application &#8211; 57dB &#8211; which you would <em>hopefully</em> never need to apply to a real recording, but nonetheless it demonstrates that Tascam&#8217;s claims are exaggerations at best; if you <em>actually</em> had the Portacapture X8&#8217;s input gain set to 0dB and recorded quiet sounds, you would in fact have problems with noise &#8211; problems that would be avoided with a correct input gain setting.</p>



<h2 class="wp-block-heading">Clipping still happens if input gain is too high</h2>



<p class="wp-block-paragraph">The recorder clearly applies actual analog amplification and can still saturate its ADCs, as shown in this composite of three gain levels.  They are (in order):  Auto gain, 35dB, 57dB.</p>



<p class="wp-block-paragraph"><em>Warning</em>: annoying, distorted sound.</p>


<div
    id="h5ap-player-2"
    data-id="h5ap-player-2"
    data-attributes="{&quot;uniqueId&quot;:&quot;h5ap1fcf0b2b&quot;,&quot;source&quot;:&quot;https:\/\/wadetregaskis.com\/wp-content\/uploads\/2025\/07\/Tascam-Portacapture-X8-w-Sennheiser-K6-ME66-clipping-demonstration.wav&quot;,&quot;primaryColor&quot;:&quot;#4A5464&quot;,&quot;controlColor&quot;:&quot;#4A5464&quot;,&quot;bgColor&quot;:&quot;#F5F5F5&quot;,&quot;loader&quot;:true,&quot;preload&quot;:&quot;auto&quot;,&quot;radius&quot;:&quot;50px&quot;,&quot;waveType&quot;:&quot;equalizer&quot;,&quot;clientId&quot;:&quot;&quot;,&quot;align&quot;:&quot;&quot;,&quot;alignment&quot;:&quot;left&quot;,&quot;poster&quot;:&quot;&quot;,&quot;title&quot;:&quot;audio title&quot;,&quot;artist&quot;:&quot;&quot;,&quot;color&quot;:&quot;#87ceeb&quot;,&quot;hoverColor&quot;:&quot;#00B3FF&quot;,&quot;skin&quot;:&quot;Default&quot;,&quot;repeat&quot;:false,&quot;autoplay&quot;:false,&quot;muted&quot;:false,&quot;saveState&quot;:false,&quot;disablePause&quot;:false,&quot;seekTime&quot;:10,&quot;startTime&quot;:0,&quot;download&quot;:true,&quot;width&quot;:&quot;100%&quot;,&quot;speed&quot;:{&quot;selected&quot;:1,&quot;speed&quot;:[&quot;0.5&quot;,&quot; 0.75&quot;,&quot; 1&quot;,&quot; 1.25&quot;,&quot; 1.5&quot;,&quot; 1.75&quot;,&quot; 2&quot;,&quot; 4&quot;,&quot; 8&quot;]},&quot;controls&quot;:{&quot;restart&quot;:false,&quot;rewind&quot;:false,&quot;play&quot;:true,&quot;fast-forward&quot;:false,&quot;progress&quot;:true,&quot;duration&quot;:false,&quot;current-time&quot;:true,&quot;mute&quot;:true,&quot;volume&quot;:true,&quot;settings&quot;:true,&quot;download&quot;:false},&quot;options&quot;:{&quot;volume&quot;:0.5},&quot;isSticky&quot;:false,&quot;CSS&quot;:&quot;&quot;,&quot;multiple_audio&quot;:false,&quot;lazyLoad&quot;:false,&quot;defaultValue&quot;:{&quot;Default&quot;:{&quot;primaryColor&quot;:&quot;#4A5464&quot;,&quot;controlColor&quot;:&quot;#4A5464&quot;,&quot;bgColor&quot;:&quot;#F5F5F5&quot;},&quot;Fusion&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;controlColor&quot;:&quot;#fff&quot;},&quot;Stamp&quot;:{&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;},&quot;Wave&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Card-1&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;controlColor&quot;:&quot;#161616&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Card-2&quot;:{&quot;primaryColor&quot;:&quot;#00FBF3&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Simple-1&quot;:{&quot;primaryColor&quot;:&quot;#00dcff&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#00dcff&quot;},&quot;Simple-2&quot;:{&quot;primaryColor&quot;:&quot;#06F7FF00&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;},&quot;Player9&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#EDEFF2&quot;},&quot;Player10&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#EDEFF2&quot;},&quot;Player11&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;}},&quot;i18n&quot;:{&quot;restart&quot;:&quot;Restart&quot;,&quot;rewind&quot;:&quot;Rewind {seektime}s&quot;,&quot;play&quot;:&quot;Play&quot;,&quot;pause&quot;:&quot;Pause&quot;,&quot;fastForward:&quot;:&quot;Forward {seektime}s&quot;,&quot;seek&quot;:&quot;Seek&quot;,&quot;seekLabel&quot;:&quot;{currentTime} of {duration}&quot;,&quot;played&quot;:&quot;Played&quot;,&quot;buffered&quot;:&quot;Buffered&quot;,&quot;currentTime:&quot;:&quot;Current time&quot;,&quot;duration&quot;:&quot;Duration&quot;,&quot;volume&quot;:&quot;Volume&quot;,&quot;mute&quot;:&quot;Mute&quot;,&quot;unmute&quot;:&quot;Unmute&quot;,&quot;enableCaptions&quot;:&quot;Enable captions&quot;,&quot;disableCaptions&quot;:&quot;Disable captions&quot;,&quot;download&quot;:&quot;Download&quot;,&quot;enterFullscreen&quot;:&quot;Enter fullscreen&quot;,&quot;exitFullscreen&quot;:&quot;Exit fullscreen&quot;,&quot;frameTitle&quot;:&quot;Player for {title}&quot;,&quot;captions&quot;:&quot;Captions&quot;,&quot;settings&quot;:&quot;Settings&quot;,&quot;pip&quot;:&quot;PIP&quot;,&quot;menuBack&quot;:&quot;Go back to previous menu&quot;,&quot;speed&quot;:&quot;Speed&quot;,&quot;normal&quot;:&quot;Normal&quot;,&quot;quality&quot;:&quot;Quality&quot;,&quot;loop&quot;:&quot;Loop&quot;,&quot;start&quot;:&quot;Start&quot;,&quot;end&quot;:&quot;End&quot;,&quot;all&quot;:&quot;All&quot;,&quot;reset&quot;:&quot;Reset&quot;,&quot;disabled&quot;:&quot;Disabled&quot;,&quot;enabled&quot;:&quot;Enabled&quot;,&quot;advertisement&quot;:&quot;Ad&quot;,&quot;qualityBadge&quot;:{&quot;2160&quot;:&quot;4K&quot;,&quot;1440&quot;:&quot;HD&quot;,&quot;1080&quot;:&quot;HD&quot;,&quot;720&quot;:&quot;HD&quot;,&quot;576&quot;:&quot;SD&quot;,&quot;480&quot;:&quot;SD&quot;}}}"
    class="wp-block-h5ap-tailwind wp-block-h5ap-audioplayer">
            <div class='h5ap_lp'>
            <div class='bar bar-1'></div>
            <div class='bar bar-1'></div>
        </div>
    </div>

<div class="wp-block-image is-style-default">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="883" height="214" src="https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-of-a-dehumidifier-at-three-different-input-gain-settings-auto-0db-and-57db-1.webp" alt="Waveforms from the Final Cut Pro timeline of Tascam Portacapture X8 recordings of a dehumidifier, at three different input gain settings (Auto, 0dB, and 57dB)" class="wp-image-8531" srcset="https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-of-a-dehumidifier-at-three-different-input-gain-settings-auto-0db-and-57db-1.webp 883w, https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-of-a-dehumidifier-at-three-different-input-gain-settings-auto-0db-and-57db-1-256x62.webp 256w, https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-of-a-dehumidifier-at-three-different-input-gain-settings-auto-0db-and-57db-1-768x186.webp 768w, https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-of-a-dehumidifier-at-three-different-input-gain-settings-auto-0db-and-57db-1@2x.webp 1766w, https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-of-a-dehumidifier-at-three-different-input-gain-settings-auto-0db-and-57db-1-256x62@2x.webp 512w" sizes="auto, (max-width: 883px) 100vw, 883px" /></figure>
</div>


<p class="wp-block-paragraph">It only went over by about 3dB (at 57dB input gain), but that was enough to destroy the input signal and make the recording unusable.</p>



<h2 class="wp-block-heading">Auto Gain still affects the recording</h2>



<p class="wp-block-paragraph">In the above tests I included the Auto Gain setting, even though it doesn&#8217;t exhibit particularly high noise nor does it clip in these simple sound environments (basically constant sound levels).  And it worked pretty well (not optimal input gain levels, but close enough for my taste).</p>



<p class="wp-block-paragraph">But, I was curious if it had any effect at all &#8211; again, reading about 32-bit floating-point recording online, you&#8217;d be forgiven for thinking Auto Gain has no impact on the <em>actual</em> recorded data.  Many people liken the format to camera raw files, and some <em>explicitly</em> state that Auto Gain has <em>no</em> impact on the bits that get written to disk.</p>



<p class="wp-block-paragraph">This is completely false, at least in the case of this Tascam Portacapture X8.  It&#8217;s trivial to see why:</p>


<div
    id="h5ap-player-3"
    data-id="h5ap-player-3"
    data-attributes="{&quot;uniqueId&quot;:&quot;h5ap8de70736&quot;,&quot;source&quot;:&quot;https:\/\/wadetregaskis.com\/wp-content\/uploads\/2025\/07\/Tascam-Portacapture-X8-w-Sennheiser-K6-ME65-Auto-Gain-vs-constant-gain.wav&quot;,&quot;primaryColor&quot;:&quot;#4A5464&quot;,&quot;controlColor&quot;:&quot;#4A5464&quot;,&quot;bgColor&quot;:&quot;#F5F5F5&quot;,&quot;loader&quot;:true,&quot;preload&quot;:&quot;auto&quot;,&quot;radius&quot;:&quot;50px&quot;,&quot;waveType&quot;:&quot;equalizer&quot;,&quot;clientId&quot;:&quot;&quot;,&quot;align&quot;:&quot;&quot;,&quot;alignment&quot;:&quot;left&quot;,&quot;poster&quot;:&quot;&quot;,&quot;title&quot;:&quot;audio title&quot;,&quot;artist&quot;:&quot;&quot;,&quot;color&quot;:&quot;#87ceeb&quot;,&quot;hoverColor&quot;:&quot;#00B3FF&quot;,&quot;skin&quot;:&quot;Default&quot;,&quot;repeat&quot;:false,&quot;autoplay&quot;:false,&quot;muted&quot;:false,&quot;saveState&quot;:false,&quot;disablePause&quot;:false,&quot;seekTime&quot;:10,&quot;startTime&quot;:0,&quot;download&quot;:true,&quot;width&quot;:&quot;100%&quot;,&quot;speed&quot;:{&quot;selected&quot;:1,&quot;speed&quot;:[&quot;0.5&quot;,&quot; 0.75&quot;,&quot; 1&quot;,&quot; 1.25&quot;,&quot; 1.5&quot;,&quot; 1.75&quot;,&quot; 2&quot;,&quot; 4&quot;,&quot; 8&quot;]},&quot;controls&quot;:{&quot;restart&quot;:false,&quot;rewind&quot;:false,&quot;play&quot;:true,&quot;fast-forward&quot;:false,&quot;progress&quot;:true,&quot;duration&quot;:false,&quot;current-time&quot;:true,&quot;mute&quot;:true,&quot;volume&quot;:true,&quot;settings&quot;:true,&quot;download&quot;:false},&quot;options&quot;:{&quot;volume&quot;:0.5},&quot;isSticky&quot;:false,&quot;CSS&quot;:&quot;&quot;,&quot;multiple_audio&quot;:false,&quot;lazyLoad&quot;:false,&quot;defaultValue&quot;:{&quot;Default&quot;:{&quot;primaryColor&quot;:&quot;#4A5464&quot;,&quot;controlColor&quot;:&quot;#4A5464&quot;,&quot;bgColor&quot;:&quot;#F5F5F5&quot;},&quot;Fusion&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;controlColor&quot;:&quot;#fff&quot;},&quot;Stamp&quot;:{&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;},&quot;Wave&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Card-1&quot;:{&quot;primaryColor&quot;:&quot;#fff&quot;,&quot;controlColor&quot;:&quot;#161616&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Card-2&quot;:{&quot;primaryColor&quot;:&quot;#00FBF3&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;},&quot;Simple-1&quot;:{&quot;primaryColor&quot;:&quot;#00dcff&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#00dcff&quot;},&quot;Simple-2&quot;:{&quot;primaryColor&quot;:&quot;#06F7FF00&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;},&quot;Player9&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#EDEFF2&quot;},&quot;Player10&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#EDEFF2&quot;},&quot;Player11&quot;:{&quot;primaryColor&quot;:&quot;#195FF6&quot;,&quot;controlColor&quot;:&quot;#fff&quot;,&quot;bgColor&quot;:&quot;#161616&quot;,&quot;textColor&quot;:&quot;#fff&quot;}},&quot;i18n&quot;:{&quot;restart&quot;:&quot;Restart&quot;,&quot;rewind&quot;:&quot;Rewind {seektime}s&quot;,&quot;play&quot;:&quot;Play&quot;,&quot;pause&quot;:&quot;Pause&quot;,&quot;fastForward:&quot;:&quot;Forward {seektime}s&quot;,&quot;seek&quot;:&quot;Seek&quot;,&quot;seekLabel&quot;:&quot;{currentTime} of {duration}&quot;,&quot;played&quot;:&quot;Played&quot;,&quot;buffered&quot;:&quot;Buffered&quot;,&quot;currentTime:&quot;:&quot;Current time&quot;,&quot;duration&quot;:&quot;Duration&quot;,&quot;volume&quot;:&quot;Volume&quot;,&quot;mute&quot;:&quot;Mute&quot;,&quot;unmute&quot;:&quot;Unmute&quot;,&quot;enableCaptions&quot;:&quot;Enable captions&quot;,&quot;disableCaptions&quot;:&quot;Disable captions&quot;,&quot;download&quot;:&quot;Download&quot;,&quot;enterFullscreen&quot;:&quot;Enter fullscreen&quot;,&quot;exitFullscreen&quot;:&quot;Exit fullscreen&quot;,&quot;frameTitle&quot;:&quot;Player for {title}&quot;,&quot;captions&quot;:&quot;Captions&quot;,&quot;settings&quot;:&quot;Settings&quot;,&quot;pip&quot;:&quot;PIP&quot;,&quot;menuBack&quot;:&quot;Go back to previous menu&quot;,&quot;speed&quot;:&quot;Speed&quot;,&quot;normal&quot;:&quot;Normal&quot;,&quot;quality&quot;:&quot;Quality&quot;,&quot;loop&quot;:&quot;Loop&quot;,&quot;start&quot;:&quot;Start&quot;,&quot;end&quot;:&quot;End&quot;,&quot;all&quot;:&quot;All&quot;,&quot;reset&quot;:&quot;Reset&quot;,&quot;disabled&quot;:&quot;Disabled&quot;,&quot;enabled&quot;:&quot;Enabled&quot;,&quot;advertisement&quot;:&quot;Ad&quot;,&quot;qualityBadge&quot;:{&quot;2160&quot;:&quot;4K&quot;,&quot;1440&quot;:&quot;HD&quot;,&quot;1080&quot;:&quot;HD&quot;,&quot;720&quot;:&quot;HD&quot;,&quot;576&quot;:&quot;SD&quot;,&quot;480&quot;:&quot;SD&quot;}}}"
    class="wp-block-h5ap-tailwind wp-block-h5ap-audioplayer">
            <div class='h5ap_lp'>
            <div class='bar bar-1'></div>
            <div class='bar bar-1'></div>
        </div>
    </div>

<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="590" height="214" src="https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-showing-the-difference-between-auto-gain-and-constant-gain.webp" alt="Waveforms from the Final Cut Pro timeline of Tascam Portacapture X8 recordings showing the difference between Auto Gain and constant gain" class="wp-image-8533" srcset="https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-showing-the-difference-between-auto-gain-and-constant-gain.webp 590w, https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-showing-the-difference-between-auto-gain-and-constant-gain-256x93.webp 256w, https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-showing-the-difference-between-auto-gain-and-constant-gain@2x.webp 1180w, https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-showing-the-difference-between-auto-gain-and-constant-gain-256x93@2x.webp 512w" sizes="auto, (max-width: 590px) 100vw, 590px" /></figure>
</div>


<p class="wp-block-paragraph">Auto Gain still does exactly what it always does &#8211; it <em>changes</em> the gain in response to the input.  That change <em>is</em> baked into the recorded audio track(s).</p>



<p class="wp-block-paragraph">So in a nutshell, 32-bit floating-point recording <em>might</em> provide slightly more flexibility in some situations, but it does <em>not</em> mean you can ignore your input level settings, it does <em>not</em> mean you can use Auto Gain in every scenario, and it does <em>not</em> mean you cannot clip.</p>



<h2 class="wp-block-heading">Addendum: Technical details</h2>



<p class="wp-block-paragraph">I tested post-production gain changes in Final Cut Pro, Logic Pro, &amp; Audacity.  All produced the exact same results (to my ears).  <a href="https://discussions.apple.com/thread/255782592" data-wpel-link="external" target="_blank" rel="external noopener">I had read that Final Cut Pro sometimes &#8216;bakes in&#8217; clipping with 32-bit float inputs</a>, as if it&#8217;s pre-rendering them down to some smaller dynamic range, so I wanted to rule out some Final Cut Pro-specific stupidity.  It&#8217;s possible that <em>all</em> these editors are doing that, but I&#8217;d be flabbergasted if that&#8217;s true.</p>



<p class="wp-block-paragraph">The &#8220;industrial noise&#8221; sample I used was my dehumidifier, which is about 66dB according to <a href="https://apps.apple.com/us/app/decibel-x-db-sound-level-meter/id448155923" data-wpel-link="external" target="_blank" rel="external noopener">DecibelX</a> on my iPhone 14 Pro.  My office room tone is about 42dB according to the same app.</p>



<p class="wp-block-paragraph">I used <a href="https://www.sennheiser.com/en-us/catalog/uncategorized/k-6/k-6-003279" data-wpel-link="external" target="_blank" rel="external noopener">Sennheiser K6</a> modules with an <a href="https://www.sennheiser.com/en-us/catalog/uncategorized/me-65/me-65-003283" data-wpel-link="external" target="_blank" rel="external noopener">ME65</a> and <a href="https://www.sennheiser.com/en-us/catalog/uncategorized/me-66/me-66-003284" data-wpel-link="external" target="_blank" rel="external noopener">ME66</a> attached, plugged into the Tascam Portacapture X8 via <a href="https://www.amazon.com/dp/B00KO8VY4O" data-wpel-link="external" target="_blank" rel="external noopener">3&#8242; Cable Matters XLR cables</a>.</p>



<p class="wp-block-paragraph">I recorded at 96kHz because that&#8217;s what I&#8217;ll use most often.  I like the aliasing headroom above 48kHz (even though of course my final outputs are almost always 44.1kHz or 48kHz), but don&#8217;t see evidence that 192kHz provides meaningful additional benefit (and it also hurts the frequency response significantly, compared with 48kHz and 96kHz, <a href="https://tascam.com/us/product/portacapture_x8/spec#:~:text=Audio%20Performance" data-wpel-link="external" target="_blank" rel="external noopener">according to Tascam</a>).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/32-bit-float-audio-recording-is-not-a-panacea/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2025/07/waveforms-from-the-final-cut-pro-timeline-of-tascam-portacapture-x8-recordings-showing-the-difference-between-auto-gain-and-constant-gain.webp" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8524</post-id>	</item>
		<item>
		<title>How to disable automatic project backups in Final Cut Pro</title>
		<link>https://wadetregaskis.com/how-to-disable-automatic-project-backups-in-final-cut-pro/</link>
					<comments>https://wadetregaskis.com/how-to-disable-automatic-project-backups-in-final-cut-pro/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 23 Jul 2025 23:22:17 +0000</pubDate>
				<category><![CDATA[Howto]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[Final Cut Pro]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8517</guid>

					<description><![CDATA[Apple does provide these instructions, although they were annoyingly hard to find (in no small part because the page title is only incidentally related to the task). In short: My projects are already covered by Time Machine and other backup mechanisms, and I tried to tolerate Final Cut Pros built-in backup system, but the damn&#8230; <a class="read-more-link" href="https://wadetregaskis.com/how-to-disable-automatic-project-backups-in-final-cut-pro/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Apple does provide <a href="https://support.apple.com/en-au/guide/final-cut-pro/ver7db6ffe77/11.1/mac/14.6" data-wpel-link="external" target="_blank" rel="external noopener">these instructions</a>, although they were annoyingly hard to find (in no small part because the page title is only incidentally related to the task).</p>



<p class="wp-block-paragraph">In short:</p>



<ol class="wp-block-list">
<li>File &gt; Library Properties (⌃⌘J).</li>



<li>Click &#8220;Modify Settings&#8221; next to &#8220;Storage Locations&#8221;.</li>



<li>Set the &#8220;Backups&#8221; option to &#8220;Do Not Save&#8221;.</li>
</ol>



<p class="wp-block-paragraph">My projects are already covered by Time Machine and other backup mechanisms, and I tried to tolerate Final Cut Pros built-in backup system, but the damn thing runs <em>so</em> often, which I know about very well because whenever it runs Final Cut Pro becomes completely unusable for several minutes.  It&#8217;s immensely irritating when it kicks in while I&#8217;m in the middle of editing.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/how-to-disable-automatic-project-backups-in-final-cut-pro/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8517</post-id>	</item>
		<item>
		<title>traceroute bad.horse</title>
		<link>https://wadetregaskis.com/traceroute-bad-horse/</link>
					<comments>https://wadetregaskis.com/traceroute-bad-horse/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Wed, 26 Mar 2025 16:08:11 +0000</pubDate>
				<category><![CDATA[Amusements]]></category>
		<category><![CDATA[bad.horse]]></category>
		<category><![CDATA[delightful]]></category>
		<category><![CDATA[Dr. Horrible's Sing-Along Blog]]></category>
		<category><![CDATA[traceroute]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8497</guid>

					<description><![CDATA[Bad horse, bad horse,
He rides across the nation,
The thoroughbred of sin… <a class="read-more-link" href="https://wadetregaskis.com/traceroute-bad-horse/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<pre class="wp-block-preformatted">…<br> 9  <strong>bad.horse</strong> (162.252.205.130)  62.196 ms  63.390 ms  63.267 ms<br>10  <strong>bad.horse</strong> (162.252.205.131)  67.397 ms  69.556 ms  71.085 ms<br>11  <strong>bad.horse</strong> (162.252.205.132)  70.426 ms  74.513 ms  75.403 ms<br>12  <strong>bad.horse</strong> (162.252.205.133)  75.012 ms  78.834 ms  86.565 ms<br>13  <strong>he.rides.across.the.nation</strong> (162.252.205.134)  80.596 ms  88.400 ms  81.847 ms<br>14  <strong>the.thoroughbred.of.sin</strong> (162.252.205.135)  97.466 ms  94.171 ms  87.484 ms<br>15  <strong>he.got.the.application</strong> (162.252.205.136)  92.750 ms  94.187 ms  93.364 ms<br>16  <strong>that.you.just.sent.in</strong> (162.252.205.137)  99.097 ms  98.912 ms  98.936 ms<br>17  <strong>it.needs.evaluation</strong> (162.252.205.138)  126.594 ms  108.474 ms  133.197 ms<br>18  <strong>so.let.the.games.begin</strong> (162.252.205.139)  126.089 ms  108.070 ms  112.346 ms<br>19  <strong>a.heinous.crime</strong> (162.252.205.140)  111.035 ms  119.682 ms  119.683 ms<br>20 <strong> a.show.of.force</strong> (162.252.205.141)  118.606 ms  120.621 ms  120.960 ms<br>21  <strong>a.murder.would.be.nice.of.course</strong> (162.252.205.142)  127.825 ms  120.987 ms  125.698 ms<br>22  <strong>bad.horse</strong> (162.252.205.143)  125.553 ms  128.730 ms  131.571 ms<br>23  <strong>bad.horse</strong> (162.252.205.144)  130.241 ms  130.788 ms  141.041 ms<br>24  <strong>bad.horse</strong> (162.252.205.145)  139.598 ms  140.064 ms  137.912 ms<br>25  <strong>he-s.bad</strong> (162.252.205.146)  141.004 ms  139.975 ms  141.036 ms<br>26  <strong>the.evil.league.of.evil</strong> (162.252.205.147)  153.193 ms  147.047 ms  147.590 ms<br>27  <strong>is.watching.so.beware</strong> (162.252.205.148)  153.920 ms  157.260 ms  154.382 ms<br>28  <strong>the.grade.that.you.receive</strong> (162.252.205.149)  161.033 ms  159.111 ms  156.247 ms<br>29  <strong>will.be.your.last.we.swear</strong> (162.252.205.150)  163.990 ms  161.895 ms  166.253 ms<br>30  <strong>so.make.the.bad.horse.gleeful</strong> (162.252.205.151)  178.317 ms  167.679 ms  162.366 ms<br>31  <strong>or.he-ll.make.you.his.mare</strong> (162.252.205.152)  172.914 ms  174.006 ms  178.214 ms<br>32  <strong>o_o</strong> (162.252.205.153)  183.017 ms  180.428 ms  175.723 ms<br>33  <strong>you-re.saddled.up</strong> (162.252.205.154)  183.340 ms  181.684 ms  184.889 ms<br>34  <strong>there-s.no.recourse</strong> (162.252.205.155)  185.933 ms  202.451 ms  199.171 ms<br>35  <strong>it-s.hi-ho.silver</strong> (162.252.205.156)  214.959 ms  191.279 ms  195.541 ms<br>36  <strong>signed.bad.horse</strong> (162.252.205.157)  227.503 ms  201.268 ms  190.395 ms</pre>



<p class="wp-block-paragraph">I discovered this <a href="https://macaw.social/@april/112362526774952167" data-wpel-link="external" target="_blank" rel="external noopener">from April King</a> via <a href="https://mastodon.social/@siracusa" data-wpel-link="external" target="_blank" rel="external noopener">John Siracusa</a>.</p>



<p class="wp-block-paragraph">If all that is lost on you, you&#8217;re in for <a href="https://drhorrible.com" data-wpel-link="external" target="_blank" rel="external noopener">a treat</a>.</p>
<!-- /wp:post-content -->]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/traceroute-bad-horse/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">8497</post-id>	</item>
	</channel>
</rss>
