Okay, so the following helped:

 1. I changed the trigger to an After trigger.  It's got a bunch more overhead (e.g. re-querying the records), but I was desperate and just trying anything different.

 2. In that After trigger, instead of doing the standard "Static Boolean"-based recursion prevention, I switched to "check the IDs you have already processed-based" recursion prevention as indicated [here][1].

New code:

	public with sharing class ContactTriggerHandler {
	...
		public static void copyDailyDumpDataAfterInsertOrUpdate(Map<Id,Contact> newContacts, Map<Id, Contact> oldContacts, Boolean isInsert) {
			// Make sure this method only runs once per set of just-inserted/updated contacts.
			if (alreadyProcessed.containsAll(newContacts.keySet())) {return;}
			alreadyProcessed.addAll(newContacts.keySet());
			
			List<Id> dumpCIds = new List<Id>();
			List<Contact> csToUpdate = new List<Contact>();
			
			for (Contact c : newContacts.values()) {
				// Cache as an "DDmp" Contact record if it was just inserted/updated by the DDmp Jitterbit load
				if (c.DailyDump_Contact_Id__c <> null &&
					c.DDmp_Jitterbit_Most_Recent_Load_DateTime__c <> null &&
					(isInsert ||
					 oldContacts == null ||
					 oldContacts.get(c.Id).DDmp_Jitterbit_Most_Recent_Load_DateTime__c == null ||
					 oldContacts.get(c.Id).DDmp_Jitterbit_Most_Recent_Load_DateTime__c < c.DDmp_Jitterbit_Most_Recent_Load_DateTime__c)
				   ) {
					   dumpCIds.add(c.Id);
				   }
			}
			
			if (dumpCIds.size()>0) {
				Boolean chgdDump = false;
				for (Contact c : [SELECT Id
								  ,PIDM__c,DDmp_Copy_of_PIDM__c
								  ,Banner_UST_ID__c,DDmp_Copy_Of_Banner_ID__c
								  ,Birthdate,DDmp_Birthdate__c
								  ,Citizenship__c,DDmp_Applicant_Citizenship_Status__c
								  ,Company_Holding_Spot__c,DDmp_Account_Name__c
								  ,Email,DDmp_Email__c
								  ,FirstName,DDmp_FirstName__c
								  ,Gender__c,DDmp_Gender__c
								  ,MailingStreet,DDmp_MailingStreetLine1__c,DDmp_MailingStreetLine2__c,DDmp_MailingStreetLine3__c
								  ,MailingCity,DDmp_MailingCity__c
								  ,MailingState,DDmp_MailingState__c
								  ,MailingPostalCode,DDmp_MailingZip__c
								  ,MailingCountry,DDmp_MailingCountry__c
								  ,Phone,OtherPhone,DDmp_HomePhone__c,DDmp_MobilePhone__c,DDmp_Work_Phone__c
								  FROM Contact WHERE Id IN :dumpCIds]) {
									  {
										  // Smart-edit various fields
										  if (c.PIDM__c==null && c.DDmp_Copy_of_PIDM__c<>null) c.PIDM__c = c.DDmp_Copy_of_PIDM__c; chgdDump=TRUE;
										  if (c.Banner_UST_ID__c==null && c.DDmp_Copy_Of_Banner_ID__c<>null) c.Banner_UST_ID__c = c.DDmp_Copy_Of_Banner_ID__c; chgdDump=TRUE;
										  if (c.Birthdate==null && c.DDmp_Birthdate__c<>null) c.Birthdate = c.DDmp_Birthdate__c; chgdDump=TRUE;
										  if (c.Citizenship__c==null && c.DDmp_Applicant_Citizenship_Status__c<>null) c.Citizenship__c = c.DDmp_Applicant_Citizenship_Status__c; chgdDump=TRUE;
										  if (c.Company_Holding_Spot__c==null && c.DDmp_Account_Name__c<>null) c.Company_Holding_Spot__c = c.DDmp_Account_Name__c; chgdDump=TRUE;
										  if (c.Email==null && c.DDmp_Email__c<>null) c.Email = c.DDmp_Email__c; chgdDump=TRUE;
										  System.debug('First names:  ' + c.FirstName + c.DDmp_FirstName__c); // DEBUG LINE ONLY
										  if ((c.FirstName==null || Test.IsRunningTest()) && c.DDmp_FirstName__c<>null) c.FirstName = c.DDmp_FirstName__c; chgdDump=TRUE; // To make debug logs easier to read, our test to make sure this works starts with a FirstName in place, unlike real data, hence the alternative check for isRunningTest for overwriting.
										  if (c.Gender__c==null && c.DDmp_Gender__c<>null) c.Gender__c = c.DDmp_Gender__c; chgdDump=TRUE;
										  if (c.MailingStreet==null && c.DDmp_MailingStreetLine1__c<>null) c.MailingStreet = c.DDmp_MailingStreetLine1__c +
											  ((c.DDmp_MailingStreetLine2__c <> null) ? ('\r\n' + c.DDmp_MailingStreetLine2__c) : '') +
											  ((c.DDmp_MailingStreetLine3__c <> null) ? ('\r\n' + c.DDmp_MailingStreetLine3__c) : ''); chgdDump=TRUE;
										  if (c.MailingCity==null && c.DDmp_MailingCity__c<>null) c.MailingCity = c.DDmp_MailingCity__c; chgdDump=TRUE;
										  if (c.MailingState==null && c.DDmp_MailingState__c<>null) c.MailingState = c.DDmp_MailingState__c; chgdDump=TRUE;
										  if (c.MailingPostalCode==null && c.DDmp_MailingZip__c<>null) c.MailingPostalCode = c.DDmp_MailingZip__c; chgdDump=TRUE;
										  if (c.MailingCountry==null && c.DDmp_MailingCountry__c<>null) c.MailingCountry = c.DDmp_MailingCountry__c; chgdDump=TRUE;
										  // Take phone number from best in preference order Home, Mobile, Work
										  if (c.Phone==null) {
											  if (c.DDmp_HomePhone__c<>null) {c.Phone=c.DDmp_HomePhone__c; chgdDump=TRUE;}
											  else if (c.DDmp_MobilePhone__c<>null) {c.Phone=c.DDmp_MobilePhone__c; chgdDump=TRUE;}
											  else c.Phone=c.DDmp_Work_Phone__c; chgdDump=TRUE;
										  }
										  // If another phone exists, take it from 2nd-best in preference order Home, Mobile, Work
										  if (c.OtherPhone == null) {
											  if (c.DDmp_HomePhone__c<>null) {c.OtherPhone = (c.DDmp_MobilePhone__c<>null) ? c.DDmp_MobilePhone__c : c.DDmp_Work_Phone__c ; chgdDump=TRUE;}
										  }
									  }
									  // If anything was smart-edited, set the record aside for updating
									  if (chgdDump) csToUpdate.add(c);
									  chgdDump=FALSE;
								  }
			}
			
			if(csToUpdate.size()>0) {
				UPDATE csToUpdate;
			} 
		}
		...
	}

I'm glad it's working, but for future reference, does anyone have any suggestions how I could achieve the same "large dataset recursion prevention" effect with a Before trigger in this context?  (There's no "Id" to latch onto and cache at the class level yet...)

  [1]: https://salesforce.stackexchange.com/questions/61613/how-to-avoid-recursive-triggers-when-trigger-set-size-is-200 "here"